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.DistrustAjaxPage;
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.TrustAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
92 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
93 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
94 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
95 import net.pterodactylus.sone.web.page.FreenetRequest;
96 import net.pterodactylus.sone.web.page.TemplateRenderer;
97 import net.pterodactylus.sone.web.pages.*;
98 import net.pterodactylus.util.notify.Notification;
99 import net.pterodactylus.util.notify.NotificationManager;
100 import net.pterodactylus.util.notify.TemplateNotification;
101 import net.pterodactylus.util.template.Template;
102 import net.pterodactylus.util.template.TemplateContextFactory;
103 import net.pterodactylus.util.web.RedirectPage;
104 import net.pterodactylus.util.web.TemplatePage;
106 import freenet.clients.http.SessionManager;
107 import freenet.clients.http.SessionManager.Session;
108 import freenet.clients.http.ToadletContext;
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;
161 private final Translation translation;
163 /** The “new Sone” notification. */
164 private final ListNotification<Sone> newSoneNotification;
166 /** The “new post” notification. */
167 private final ListNotification<Post> newPostNotification;
169 /** The “new reply” notification. */
170 private final ListNotification<PostReply> newReplyNotification;
172 /** The invisible “local post” notification. */
173 private final ListNotification<Post> localPostNotification;
175 /** The invisible “local reply” notification. */
176 private final ListNotification<PostReply> localReplyNotification;
178 /** The “you have been mentioned” notification. */
179 private final ListNotification<Post> mentionNotification;
181 /** Notifications for sone inserts. */
182 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
184 /** Sone locked notification ticker objects. */
185 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
187 /** The “Sone locked” notification. */
188 private final ListNotification<Sone> lockedSonesNotification;
190 /** The “new version” notification. */
191 private final TemplateNotification newVersionNotification;
193 /** The “inserting images” notification. */
194 private final ListNotification<Image> insertingImagesNotification;
196 /** The “inserted images” notification. */
197 private final ListNotification<Image> insertedImagesNotification;
199 /** The “image insert failed” notification. */
200 private final ListNotification<Image> imageInsertFailedNotification;
202 /** Scheduled executor for time-based notifications. */
203 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
206 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
207 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
208 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
209 TemplateRenderer templateRenderer,
210 ParserFilter parserFilter, ShortenFilter shortenFilter,
211 RenderFilter renderFilter,
212 LinkedElementRenderFilter linkedElementRenderFilter,
213 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter) {
214 this.sonePlugin = sonePlugin;
215 this.loaders = loaders;
216 this.listNotificationFilter = listNotificationFilter;
217 this.postVisibilityFilter = postVisibilityFilter;
218 this.replyVisibilityFilter = replyVisibilityFilter;
219 this.elementLoader = elementLoader;
220 this.templateRenderer = templateRenderer;
221 this.parserFilter = parserFilter;
222 this.shortenFilter = shortenFilter;
223 this.renderFilter = renderFilter;
224 this.linkedElementRenderFilter = linkedElementRenderFilter;
225 this.pageToadletRegistry = pageToadletRegistry;
226 this.metricRegistry = metricRegistry;
227 this.l10nFilter = l10nFilter;
228 this.translation = translation;
229 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
230 soneTextParser = new SoneTextParser(getCore(), getCore());
232 this.templateContextFactory = templateContextFactory;
233 templateContextFactory.addTemplateObject("webInterface", this);
234 templateContextFactory.addTemplateObject("formPassword", formPassword);
236 /* create notifications. */
237 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
238 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
240 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
241 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
243 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
244 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
246 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
247 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
249 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
250 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
252 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
253 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
255 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
256 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
258 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
259 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
261 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
262 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
264 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
265 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
267 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
268 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
276 * Returns the Sone core used by the Sone plugin.
278 * @return The Sone core
281 public Core getCore() {
282 return sonePlugin.core();
286 * Returns the template context factory of the web interface.
288 * @return The template context factory
290 public TemplateContextFactory getTemplateContextFactory() {
291 return templateContextFactory;
294 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
295 return getSessionManager().useSession(toadletContenxt);
298 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
299 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
300 if (session == null) {
301 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
306 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
307 Collection<Sone> localSones = getCore().getLocalSones();
308 if (localSones.size() == 1) {
309 return localSones.iterator().next();
311 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
314 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
315 Collection<Sone> localSones = getCore().getLocalSones();
316 if (localSones.size() == 1) {
317 return localSones.iterator().next();
319 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
323 * Returns the currently logged in Sone.
327 * @return The currently logged in Sone, or {@code null} if no Sone is
328 * currently logged in
330 private Sone getCurrentSone(Session session) {
331 if (session == null) {
334 String soneId = (String) session.getAttribute("Sone.CurrentSone");
335 if (soneId == null) {
338 return getCore().getLocalSone(soneId);
343 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
344 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
348 * Sets the currently logged in Sone.
350 * @param toadletContext
351 * The toadlet context
353 * The Sone to set as currently logged in
356 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
357 Session session = getOrCreateCurrentSession(toadletContext);
359 session.removeAttribute("Sone.CurrentSone");
361 session.setAttribute("Sone.CurrentSone", sone.getId());
366 * Returns the notification manager.
368 * @return The notification manager
370 public NotificationManager getNotifications() {
371 return notificationManager;
375 public Optional<Notification> getNotification(@Nonnull String notificationId) {
376 return Optional.fromNullable(notificationManager.getNotification(notificationId));
380 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
381 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
384 public Translation getTranslation() {
389 * Returns the session manager of the node.
391 * @return The node’s session manager
393 public SessionManager getSessionManager() {
394 return sonePlugin.pluginRespirator().getSessionManager("Sone");
398 * Returns the node’s form password.
400 * @return The form password
402 public String getFormPassword() {
407 * Returns the posts that have been announced as new in the
408 * {@link #newPostNotification}.
410 * @return The new posts
412 public Set<Post> getNewPosts() {
413 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
417 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
418 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
419 .addAll(newPostNotification.getElements())
420 .addAll(localPostNotification.getElements())
422 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
426 * Returns the replies that have been announced as new in the
427 * {@link #newReplyNotification}.
429 * @return The new replies
431 public Set<PostReply> getNewReplies() {
432 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
436 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
437 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
438 .addAll(newReplyNotification.getElements())
439 .addAll(localReplyNotification.getElements())
441 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
445 * Sets whether the current start of the plugin is the first start. It is
446 * considered a first start if the configuration file does not exist.
449 * {@code true} if no configuration file existed when Sone was
450 * loaded, {@code false} otherwise
452 public void setFirstStart(boolean firstStart) {
454 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
455 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
456 notificationManager.addNotification(firstStartNotification);
461 * Sets whether Sone was started with a fresh configuration file.
464 * {@code true} if Sone was started with a fresh configuration,
465 * {@code false} if the existing configuration could be read
467 public void setNewConfig(boolean newConfig) {
468 if (newConfig && !hasFirstStartNotification()) {
469 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
470 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
471 notificationManager.addNotification(configNotReadNotification);
480 * Returns whether the first start notification is currently displayed.
482 * @return {@code true} if the first-start notification is currently
483 * displayed, {@code false} otherwise
485 private boolean hasFirstStartNotification() {
486 return notificationManager.getNotification("first-start-notification") != null;
494 * Starts the web interface and registers all toadlets.
496 public void start() {
499 /* notification templates. */
500 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
502 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
503 notificationManager.addNotification(startupNotification);
505 ticker.schedule(new Runnable() {
509 startupNotification.dismiss();
511 }, 2, TimeUnit.MINUTES);
513 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
514 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
515 ticker.scheduleAtFixedRate(new Runnable() {
518 @SuppressWarnings("synthetic-access")
520 if (getCore().getIdentityManager().isConnected()) {
521 wotMissingNotification.dismiss();
523 notificationManager.addNotification(wotMissingNotification);
527 }, 15, 15, TimeUnit.SECONDS);
531 * Stops the web interface and unregisters all toadlets.
534 pageToadletRegistry.unregisterToadlets();
535 ticker.shutdownNow();
543 * Register all toadlets.
545 private void registerToadlets() {
546 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
547 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
548 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
550 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
551 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
552 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
587 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
591 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
592 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
593 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
594 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
595 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
596 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
597 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
598 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
599 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
600 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
601 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
602 pageToadletRegistry.addPage(new GetImagePage(this));
603 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
604 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
605 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
606 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
607 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
608 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
609 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
610 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
611 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
612 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
613 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
614 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
615 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
616 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
617 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
618 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
619 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
620 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
621 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
622 pageToadletRegistry.addPage(new TrustAjaxPage(this));
623 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
624 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
625 pageToadletRegistry.addPage(new LikeAjaxPage(this));
626 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
627 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
628 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
629 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
630 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
631 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
632 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
634 pageToadletRegistry.registerToadlets();
638 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
639 * {@link SonePart}s in the given text (after parsing it using
640 * {@link SoneTextParser}).
644 * @return All mentioned local Sones
646 private Collection<Sone> getMentionedSones(String text) {
647 /* we need no context to find mentioned Sones. */
648 Set<Sone> mentionedSones = new HashSet<>();
649 for (Part part : soneTextParser.parse(text, null)) {
650 if (part instanceof SonePart) {
651 mentionedSones.add(((SonePart) part).getSone());
654 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
658 * Returns the Sone insert notification for the given Sone. If no
659 * notification for the given Sone exists, a new notification is created and
663 * The Sone to get the insert notification for
664 * @return The Sone insert notification
666 private TemplateNotification getSoneInsertNotification(Sone sone) {
667 synchronized (soneInsertNotifications) {
668 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
669 if (templateNotification == null) {
670 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
671 templateNotification.set("insertSone", sone);
672 soneInsertNotifications.put(sone, templateNotification);
674 return templateNotification;
678 private boolean localSoneMentionedInNewPostOrReply(Post post) {
679 if (!post.getSone().isLocal()) {
680 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
684 for (PostReply postReply : getCore().getReplies(post.getId())) {
685 if (postReply.getSone().isLocal()) {
688 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
700 * Notifies the web interface that a new {@link Sone} was found.
702 * @param newSoneFoundEvent
706 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
707 newSoneNotification.add(newSoneFoundEvent.getSone());
708 if (!hasFirstStartNotification()) {
709 notificationManager.addNotification(newSoneNotification);
714 * Notifies the web interface that a new {@link Post} was found.
716 * @param newPostFoundEvent
720 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
721 Post post = newPostFoundEvent.getPost();
722 boolean isLocal = post.getSone().isLocal();
724 localPostNotification.add(post);
726 newPostNotification.add(post);
728 if (!hasFirstStartNotification()) {
729 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
730 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
731 mentionNotification.add(post);
732 notificationManager.addNotification(mentionNotification);
735 getCore().markPostKnown(post);
740 * Notifies the web interface that a new {@link PostReply} was found.
742 * @param newPostReplyFoundEvent
746 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
747 PostReply reply = newPostReplyFoundEvent.getPostReply();
748 boolean isLocal = reply.getSone().isLocal();
750 localReplyNotification.add(reply);
752 newReplyNotification.add(reply);
754 if (!hasFirstStartNotification()) {
755 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
756 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
757 mentionNotification.add(reply.getPost().get());
758 notificationManager.addNotification(mentionNotification);
761 getCore().markReplyKnown(reply);
766 * Notifies the web interface that a {@link Sone} was marked as known.
768 * @param markSoneKnownEvent
772 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
773 newSoneNotification.remove(markSoneKnownEvent.getSone());
777 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
778 removePost(markPostKnownEvent.getPost());
782 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
783 removeReply(markPostReplyKnownEvent.getPostReply());
787 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
788 newSoneNotification.remove(soneRemovedEvent.getSone());
792 public void postRemoved(PostRemovedEvent postRemovedEvent) {
793 removePost(postRemovedEvent.getPost());
796 private void removePost(Post post) {
797 newPostNotification.remove(post);
798 localPostNotification.remove(post);
799 if (!localSoneMentionedInNewPostOrReply(post)) {
800 mentionNotification.remove(post);
805 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
806 removeReply(postReplyRemovedEvent.getPostReply());
809 private void removeReply(PostReply reply) {
810 newReplyNotification.remove(reply);
811 localReplyNotification.remove(reply);
812 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
813 mentionNotification.remove(reply.getPost().get());
818 * Notifies the web interface that a Sone was locked.
820 * @param soneLockedEvent
824 public void soneLocked(SoneLockedEvent soneLockedEvent) {
825 final Sone sone = soneLockedEvent.getSone();
826 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
829 @SuppressWarnings("synthetic-access")
831 lockedSonesNotification.add(sone);
832 notificationManager.addNotification(lockedSonesNotification);
834 }, 5, TimeUnit.MINUTES);
835 lockedSonesTickerObjects.put(sone, tickerObject);
839 * Notifies the web interface that a Sone was unlocked.
841 * @param soneUnlockedEvent
845 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
846 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
847 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
851 * Notifies the web interface that a {@link Sone} is being inserted.
853 * @param soneInsertingEvent
857 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
858 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
859 soneInsertNotification.set("soneStatus", "inserting");
860 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
861 notificationManager.addNotification(soneInsertNotification);
866 * Notifies the web interface that a {@link Sone} was inserted.
868 * @param soneInsertedEvent
872 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
873 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
874 soneInsertNotification.set("soneStatus", "inserted");
875 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
876 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
877 notificationManager.addNotification(soneInsertNotification);
882 * Notifies the web interface that a {@link Sone} insert was aborted.
884 * @param soneInsertAbortedEvent
888 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
889 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
890 soneInsertNotification.set("soneStatus", "insert-aborted");
891 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
892 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
893 notificationManager.addNotification(soneInsertNotification);
898 * Notifies the web interface that a new Sone version was found.
900 * @param updateFoundEvent
904 public void updateFound(UpdateFoundEvent updateFoundEvent) {
905 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
906 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
907 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
908 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
909 notificationManager.addNotification(newVersionNotification);
913 * Notifies the web interface that an image insert was started
915 * @param imageInsertStartedEvent
919 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
920 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
921 notificationManager.addNotification(insertingImagesNotification);
925 * Notifies the web interface that an {@link Image} insert was aborted.
927 * @param imageInsertAbortedEvent
931 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
932 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
936 * Notifies the web interface that an {@link Image} insert is finished.
938 * @param imageInsertFinishedEvent
942 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
943 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
944 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
945 notificationManager.addNotification(insertedImagesNotification);
949 * Notifies the web interface that an {@link Image} insert has failed.
951 * @param imageInsertFailedEvent
955 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
956 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
957 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
958 notificationManager.addNotification(imageInsertFailedNotification);
962 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
963 pageToadletRegistry.activateDebugMode();