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.main.Loaders;
48 import net.pterodactylus.sone.main.PluginHomepage;
49 import net.pterodactylus.sone.main.PluginVersion;
50 import net.pterodactylus.sone.main.PluginYear;
51 import net.pterodactylus.sone.main.SonePlugin;
52 import net.pterodactylus.sone.notify.ListNotification;
53 import net.pterodactylus.sone.notify.ListNotificationFilter;
54 import net.pterodactylus.sone.notify.PostVisibilityFilter;
55 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
56 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
57 import net.pterodactylus.sone.template.ParserFilter;
58 import net.pterodactylus.sone.template.RenderFilter;
59 import net.pterodactylus.sone.template.ShortenFilter;
60 import net.pterodactylus.sone.text.Part;
61 import net.pterodactylus.sone.text.SonePart;
62 import net.pterodactylus.sone.text.SoneTextParser;
63 import net.pterodactylus.sone.text.TimeTextConverter;
64 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
65 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
66 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
67 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
68 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
71 import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
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.TrustAjaxPage;
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.ajax.UntrustAjaxPage;
94 import net.pterodactylus.sone.web.page.FreenetRequest;
95 import net.pterodactylus.sone.web.page.TemplateRenderer;
96 import net.pterodactylus.sone.web.pages.*;
97 import net.pterodactylus.util.notify.Notification;
98 import net.pterodactylus.util.notify.NotificationManager;
99 import net.pterodactylus.util.notify.TemplateNotification;
100 import net.pterodactylus.util.template.Template;
101 import net.pterodactylus.util.template.TemplateContextFactory;
102 import net.pterodactylus.util.web.RedirectPage;
103 import net.pterodactylus.util.web.TemplatePage;
105 import freenet.clients.http.SessionManager;
106 import freenet.clients.http.SessionManager.Session;
107 import freenet.clients.http.ToadletContext;
108 import freenet.l10n.BaseL10n;
110 import com.codahale.metrics.*;
111 import com.google.common.base.Optional;
112 import com.google.common.collect.Collections2;
113 import com.google.common.collect.ImmutableSet;
114 import com.google.common.eventbus.Subscribe;
115 import com.google.inject.Inject;
118 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
119 * references to l10n helpers.
121 public class WebInterface implements SessionProvider {
124 private static final Logger logger = getLogger(WebInterface.class.getName());
126 /** The loaders for templates, pages, and classpath providers. */
127 private final Loaders loaders;
129 /** The notification manager. */
130 private final NotificationManager notificationManager = new NotificationManager();
132 /** The Sone plugin. */
133 private final SonePlugin sonePlugin;
135 /** The form password. */
136 private final String formPassword;
138 /** The template context factory. */
139 private final TemplateContextFactory templateContextFactory;
140 private final TemplateRenderer templateRenderer;
142 /** The Sone text parser. */
143 private final SoneTextParser soneTextParser;
145 /** The parser filter. */
146 private final ParserFilter parserFilter;
147 private final ShortenFilter shortenFilter;
148 private final RenderFilter renderFilter;
150 private final ListNotificationFilter listNotificationFilter;
151 private final PostVisibilityFilter postVisibilityFilter;
152 private final ReplyVisibilityFilter replyVisibilityFilter;
154 private final ElementLoader elementLoader;
155 private final LinkedElementRenderFilter linkedElementRenderFilter;
156 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
157 private final L10nFilter l10nFilter;
159 private final PageToadletRegistry pageToadletRegistry;
160 private final MetricRegistry metricRegistry;
162 /** The “new Sone” notification. */
163 private final ListNotification<Sone> newSoneNotification;
165 /** The “new post” notification. */
166 private final ListNotification<Post> newPostNotification;
168 /** The “new reply” notification. */
169 private final ListNotification<PostReply> newReplyNotification;
171 /** The invisible “local post” notification. */
172 private final ListNotification<Post> localPostNotification;
174 /** The invisible “local reply” notification. */
175 private final ListNotification<PostReply> localReplyNotification;
177 /** The “you have been mentioned” notification. */
178 private final ListNotification<Post> mentionNotification;
180 /** Notifications for sone inserts. */
181 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
183 /** Sone locked notification ticker objects. */
184 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
186 /** The “Sone locked” notification. */
187 private final ListNotification<Sone> lockedSonesNotification;
189 /** The “new version” notification. */
190 private final TemplateNotification newVersionNotification;
192 /** The “inserting images” notification. */
193 private final ListNotification<Image> insertingImagesNotification;
195 /** The “inserted images” notification. */
196 private final ListNotification<Image> insertedImagesNotification;
198 /** The “image insert failed” notification. */
199 private final ListNotification<Image> imageInsertFailedNotification;
201 /** Scheduled executor for time-based notifications. */
202 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
205 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
206 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
207 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
208 TemplateRenderer templateRenderer,
209 ParserFilter parserFilter, ShortenFilter shortenFilter,
210 RenderFilter renderFilter,
211 LinkedElementRenderFilter linkedElementRenderFilter,
212 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry) {
213 this.sonePlugin = sonePlugin;
214 this.loaders = loaders;
215 this.listNotificationFilter = listNotificationFilter;
216 this.postVisibilityFilter = postVisibilityFilter;
217 this.replyVisibilityFilter = replyVisibilityFilter;
218 this.elementLoader = elementLoader;
219 this.templateRenderer = templateRenderer;
220 this.parserFilter = parserFilter;
221 this.shortenFilter = shortenFilter;
222 this.renderFilter = renderFilter;
223 this.linkedElementRenderFilter = linkedElementRenderFilter;
224 this.pageToadletRegistry = pageToadletRegistry;
225 this.metricRegistry = metricRegistry;
226 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
227 soneTextParser = new SoneTextParser(getCore(), getCore());
228 l10nFilter = new L10nFilter(getL10n());
230 this.templateContextFactory = templateContextFactory;
231 templateContextFactory.addTemplateObject("webInterface", this);
232 templateContextFactory.addTemplateObject("formPassword", formPassword);
234 /* create notifications. */
235 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
236 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
238 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
239 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
241 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
242 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
244 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
245 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
247 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
248 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
250 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
251 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
253 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
254 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
256 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
257 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
259 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
260 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
262 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
263 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
265 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
266 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
274 * Returns the Sone core used by the Sone plugin.
276 * @return The Sone core
279 public Core getCore() {
280 return sonePlugin.core();
284 * Returns the template context factory of the web interface.
286 * @return The template context factory
288 public TemplateContextFactory getTemplateContextFactory() {
289 return templateContextFactory;
292 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
293 return getSessionManager().useSession(toadletContenxt);
296 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
297 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
298 if (session == null) {
299 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
304 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
305 Collection<Sone> localSones = getCore().getLocalSones();
306 if (localSones.size() == 1) {
307 return localSones.iterator().next();
309 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
312 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
313 Collection<Sone> localSones = getCore().getLocalSones();
314 if (localSones.size() == 1) {
315 return localSones.iterator().next();
317 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
321 * Returns the currently logged in Sone.
325 * @return The currently logged in Sone, or {@code null} if no Sone is
326 * currently logged in
328 private Sone getCurrentSone(Session session) {
329 if (session == null) {
332 String soneId = (String) session.getAttribute("Sone.CurrentSone");
333 if (soneId == null) {
336 return getCore().getLocalSone(soneId);
341 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
342 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
346 * Sets the currently logged in Sone.
348 * @param toadletContext
349 * The toadlet context
351 * The Sone to set as currently logged in
354 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
355 Session session = getOrCreateCurrentSession(toadletContext);
357 session.removeAttribute("Sone.CurrentSone");
359 session.setAttribute("Sone.CurrentSone", sone.getId());
364 * Returns the notification manager.
366 * @return The notification manager
368 public NotificationManager getNotifications() {
369 return notificationManager;
373 public Optional<Notification> getNotification(@Nonnull String notificationId) {
374 return Optional.fromNullable(notificationManager.getNotification(notificationId));
378 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
379 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
383 * Returns the l10n helper of the node.
385 * @return The node’s l10n helper
387 public BaseL10n getL10n() {
388 return sonePlugin.l10n().getBase();
392 * Returns the session manager of the node.
394 * @return The node’s session manager
396 public SessionManager getSessionManager() {
397 return sonePlugin.pluginRespirator().getSessionManager("Sone");
401 * Returns the node’s form password.
403 * @return The form password
405 public String getFormPassword() {
410 * Returns the posts that have been announced as new in the
411 * {@link #newPostNotification}.
413 * @return The new posts
415 public Set<Post> getNewPosts() {
416 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
420 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
421 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
422 .addAll(newPostNotification.getElements())
423 .addAll(localPostNotification.getElements())
425 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
429 * Returns the replies that have been announced as new in the
430 * {@link #newReplyNotification}.
432 * @return The new replies
434 public Set<PostReply> getNewReplies() {
435 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
439 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
440 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
441 .addAll(newReplyNotification.getElements())
442 .addAll(localReplyNotification.getElements())
444 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
448 * Sets whether the current start of the plugin is the first start. It is
449 * considered a first start if the configuration file does not exist.
452 * {@code true} if no configuration file existed when Sone was
453 * loaded, {@code false} otherwise
455 public void setFirstStart(boolean firstStart) {
457 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
458 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
459 notificationManager.addNotification(firstStartNotification);
464 * Sets whether Sone was started with a fresh configuration file.
467 * {@code true} if Sone was started with a fresh configuration,
468 * {@code false} if the existing configuration could be read
470 public void setNewConfig(boolean newConfig) {
471 if (newConfig && !hasFirstStartNotification()) {
472 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
473 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
474 notificationManager.addNotification(configNotReadNotification);
483 * Returns whether the first start notification is currently displayed.
485 * @return {@code true} if the first-start notification is currently
486 * displayed, {@code false} otherwise
488 private boolean hasFirstStartNotification() {
489 return notificationManager.getNotification("first-start-notification") != null;
497 * Starts the web interface and registers all toadlets.
499 public void start() {
502 /* notification templates. */
503 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
505 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
506 notificationManager.addNotification(startupNotification);
508 ticker.schedule(new Runnable() {
512 startupNotification.dismiss();
514 }, 2, TimeUnit.MINUTES);
516 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
517 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
518 ticker.scheduleAtFixedRate(new Runnable() {
521 @SuppressWarnings("synthetic-access")
523 if (getCore().getIdentityManager().isConnected()) {
524 wotMissingNotification.dismiss();
526 notificationManager.addNotification(wotMissingNotification);
530 }, 15, 15, TimeUnit.SECONDS);
534 * Stops the web interface and unregisters all toadlets.
537 pageToadletRegistry.unregisterToadlets();
538 ticker.shutdownNow();
546 * Register all toadlets.
548 private void registerToadlets() {
549 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
550 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
551 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
553 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
554 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
555 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
587 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
591 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
592 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
593 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
594 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
595 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
596 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
597 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
598 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
599 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
600 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
601 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
602 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
603 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
604 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
605 pageToadletRegistry.addPage(new GetImagePage(this));
606 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
607 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
608 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
609 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
610 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
611 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
612 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
613 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
614 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
615 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
616 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
617 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
618 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
619 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
620 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
621 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
622 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
623 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
624 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
625 pageToadletRegistry.addPage(new TrustAjaxPage(this));
626 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
627 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
628 pageToadletRegistry.addPage(new LikeAjaxPage(this));
629 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
630 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
631 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
632 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
633 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
634 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
635 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
637 pageToadletRegistry.registerToadlets();
641 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
642 * {@link SonePart}s in the given text (after parsing it using
643 * {@link SoneTextParser}).
647 * @return All mentioned local Sones
649 private Collection<Sone> getMentionedSones(String text) {
650 /* we need no context to find mentioned Sones. */
651 Set<Sone> mentionedSones = new HashSet<>();
652 for (Part part : soneTextParser.parse(text, null)) {
653 if (part instanceof SonePart) {
654 mentionedSones.add(((SonePart) part).getSone());
657 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
661 * Returns the Sone insert notification for the given Sone. If no
662 * notification for the given Sone exists, a new notification is created and
666 * The Sone to get the insert notification for
667 * @return The Sone insert notification
669 private TemplateNotification getSoneInsertNotification(Sone sone) {
670 synchronized (soneInsertNotifications) {
671 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
672 if (templateNotification == null) {
673 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
674 templateNotification.set("insertSone", sone);
675 soneInsertNotifications.put(sone, templateNotification);
677 return templateNotification;
681 private boolean localSoneMentionedInNewPostOrReply(Post post) {
682 if (!post.getSone().isLocal()) {
683 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
687 for (PostReply postReply : getCore().getReplies(post.getId())) {
688 if (postReply.getSone().isLocal()) {
691 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
703 * Notifies the web interface that a new {@link Sone} was found.
705 * @param newSoneFoundEvent
709 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
710 newSoneNotification.add(newSoneFoundEvent.getSone());
711 if (!hasFirstStartNotification()) {
712 notificationManager.addNotification(newSoneNotification);
717 * Notifies the web interface that a new {@link Post} was found.
719 * @param newPostFoundEvent
723 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
724 Post post = newPostFoundEvent.getPost();
725 boolean isLocal = post.getSone().isLocal();
727 localPostNotification.add(post);
729 newPostNotification.add(post);
731 if (!hasFirstStartNotification()) {
732 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
733 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
734 mentionNotification.add(post);
735 notificationManager.addNotification(mentionNotification);
738 getCore().markPostKnown(post);
743 * Notifies the web interface that a new {@link PostReply} was found.
745 * @param newPostReplyFoundEvent
749 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
750 PostReply reply = newPostReplyFoundEvent.getPostReply();
751 boolean isLocal = reply.getSone().isLocal();
753 localReplyNotification.add(reply);
755 newReplyNotification.add(reply);
757 if (!hasFirstStartNotification()) {
758 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
759 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
760 mentionNotification.add(reply.getPost().get());
761 notificationManager.addNotification(mentionNotification);
764 getCore().markReplyKnown(reply);
769 * Notifies the web interface that a {@link Sone} was marked as known.
771 * @param markSoneKnownEvent
775 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
776 newSoneNotification.remove(markSoneKnownEvent.getSone());
780 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
781 removePost(markPostKnownEvent.getPost());
785 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
786 removeReply(markPostReplyKnownEvent.getPostReply());
790 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
791 newSoneNotification.remove(soneRemovedEvent.getSone());
795 public void postRemoved(PostRemovedEvent postRemovedEvent) {
796 removePost(postRemovedEvent.getPost());
799 private void removePost(Post post) {
800 newPostNotification.remove(post);
801 localPostNotification.remove(post);
802 if (!localSoneMentionedInNewPostOrReply(post)) {
803 mentionNotification.remove(post);
808 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
809 removeReply(postReplyRemovedEvent.getPostReply());
812 private void removeReply(PostReply reply) {
813 newReplyNotification.remove(reply);
814 localReplyNotification.remove(reply);
815 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
816 mentionNotification.remove(reply.getPost().get());
821 * Notifies the web interface that a Sone was locked.
823 * @param soneLockedEvent
827 public void soneLocked(SoneLockedEvent soneLockedEvent) {
828 final Sone sone = soneLockedEvent.getSone();
829 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
832 @SuppressWarnings("synthetic-access")
834 lockedSonesNotification.add(sone);
835 notificationManager.addNotification(lockedSonesNotification);
837 }, 5, TimeUnit.MINUTES);
838 lockedSonesTickerObjects.put(sone, tickerObject);
842 * Notifies the web interface that a Sone was unlocked.
844 * @param soneUnlockedEvent
848 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
849 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
850 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
854 * Notifies the web interface that a {@link Sone} is being inserted.
856 * @param soneInsertingEvent
860 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
861 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
862 soneInsertNotification.set("soneStatus", "inserting");
863 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
864 notificationManager.addNotification(soneInsertNotification);
869 * Notifies the web interface that a {@link Sone} was inserted.
871 * @param soneInsertedEvent
875 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
876 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
877 soneInsertNotification.set("soneStatus", "inserted");
878 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
879 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
880 notificationManager.addNotification(soneInsertNotification);
885 * Notifies the web interface that a {@link Sone} insert was aborted.
887 * @param soneInsertAbortedEvent
891 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
892 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
893 soneInsertNotification.set("soneStatus", "insert-aborted");
894 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
895 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
896 notificationManager.addNotification(soneInsertNotification);
901 * Notifies the web interface that a new Sone version was found.
903 * @param updateFoundEvent
907 public void updateFound(UpdateFoundEvent updateFoundEvent) {
908 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
909 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
910 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
911 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
912 notificationManager.addNotification(newVersionNotification);
916 * Notifies the web interface that an image insert was started
918 * @param imageInsertStartedEvent
922 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
923 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
924 notificationManager.addNotification(insertingImagesNotification);
928 * Notifies the web interface that an {@link Image} insert was aborted.
930 * @param imageInsertAbortedEvent
934 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
935 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
939 * Notifies the web interface that an {@link Image} insert is finished.
941 * @param imageInsertFinishedEvent
945 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
946 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
947 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
948 notificationManager.addNotification(insertedImagesNotification);
952 * Notifies the web interface that an {@link Image} insert has failed.
954 * @param imageInsertFailedEvent
958 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
959 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
960 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
961 notificationManager.addNotification(imageInsertFailedNotification);
965 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
966 pageToadletRegistry.activateDebugMode();