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 = new 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 this.sonePlugin = sonePlugin;
212 this.loaders = loaders;
213 this.listNotificationFilter = listNotificationFilter;
214 this.postVisibilityFilter = postVisibilityFilter;
215 this.replyVisibilityFilter = replyVisibilityFilter;
216 this.elementLoader = elementLoader;
217 this.templateRenderer = templateRenderer;
218 this.parserFilter = parserFilter;
219 this.shortenFilter = shortenFilter;
220 this.renderFilter = renderFilter;
221 this.linkedElementRenderFilter = linkedElementRenderFilter;
222 this.pageToadletRegistry = pageToadletRegistry;
223 this.metricRegistry = metricRegistry;
224 this.l10nFilter = l10nFilter;
225 this.translation = translation;
226 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
227 soneTextParser = new SoneTextParser(getCore(), getCore());
229 this.templateContextFactory = templateContextFactory;
230 templateContextFactory.addTemplateObject("webInterface", this);
231 templateContextFactory.addTemplateObject("formPassword", formPassword);
233 /* create notifications. */
234 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
235 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
237 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
238 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
240 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
241 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
243 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
244 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
246 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
247 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
249 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
250 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
252 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
253 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
255 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
256 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
258 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
259 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
261 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
262 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
264 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
265 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
273 * Returns the Sone core used by the Sone plugin.
275 * @return The Sone core
278 public Core getCore() {
279 return sonePlugin.core();
283 * Returns the template context factory of the web interface.
285 * @return The template context factory
287 public TemplateContextFactory getTemplateContextFactory() {
288 return templateContextFactory;
291 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
292 return getSessionManager().useSession(toadletContenxt);
295 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
296 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
297 if (session == null) {
298 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
303 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
304 Collection<Sone> localSones = getCore().getLocalSones();
305 if (localSones.size() == 1) {
306 return localSones.iterator().next();
308 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
311 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
312 Collection<Sone> localSones = getCore().getLocalSones();
313 if (localSones.size() == 1) {
314 return localSones.iterator().next();
316 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
320 * Returns the currently logged in Sone.
324 * @return The currently logged in Sone, or {@code null} if no Sone is
325 * currently logged in
327 private Sone getCurrentSone(Session session) {
328 if (session == null) {
331 String soneId = (String) session.getAttribute("Sone.CurrentSone");
332 if (soneId == null) {
335 return getCore().getLocalSone(soneId);
340 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
341 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
345 * Sets the currently logged in Sone.
347 * @param toadletContext
348 * The toadlet context
350 * The Sone to set as currently logged in
353 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
354 Session session = getOrCreateCurrentSession(toadletContext);
356 session.removeAttribute("Sone.CurrentSone");
358 session.setAttribute("Sone.CurrentSone", sone.getId());
363 * Returns the notification manager.
365 * @return The notification manager
367 public NotificationManager getNotifications() {
368 return notificationManager;
372 public Optional<Notification> getNotification(@Nonnull String notificationId) {
373 return Optional.fromNullable(notificationManager.getNotification(notificationId));
377 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
378 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
381 public Translation getTranslation() {
386 * Returns the session manager of the node.
388 * @return The node’s session manager
390 public SessionManager getSessionManager() {
391 return sonePlugin.pluginRespirator().getSessionManager("Sone");
395 * Returns the node’s form password.
397 * @return The form password
399 public String getFormPassword() {
404 * Returns the posts that have been announced as new in the
405 * {@link #newPostNotification}.
407 * @return The new posts
409 public Set<Post> getNewPosts() {
410 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
414 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
415 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
416 .addAll(newPostNotification.getElements())
417 .addAll(localPostNotification.getElements())
419 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
423 * Returns the replies that have been announced as new in the
424 * {@link #newReplyNotification}.
426 * @return The new replies
428 public Set<PostReply> getNewReplies() {
429 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
433 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
434 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
435 .addAll(newReplyNotification.getElements())
436 .addAll(localReplyNotification.getElements())
438 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
442 * Sets whether the current start of the plugin is the first start. It is
443 * considered a first start if the configuration file does not exist.
446 * {@code true} if no configuration file existed when Sone was
447 * loaded, {@code false} otherwise
449 public void setFirstStart(boolean firstStart) {
451 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
452 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
453 notificationManager.addNotification(firstStartNotification);
458 * Sets whether Sone was started with a fresh configuration file.
461 * {@code true} if Sone was started with a fresh configuration,
462 * {@code false} if the existing configuration could be read
464 public void setNewConfig(boolean newConfig) {
465 if (newConfig && !hasFirstStartNotification()) {
466 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
467 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
468 notificationManager.addNotification(configNotReadNotification);
477 * Returns whether the first start notification is currently displayed.
479 * @return {@code true} if the first-start notification is currently
480 * displayed, {@code false} otherwise
482 private boolean hasFirstStartNotification() {
483 return notificationManager.getNotification("first-start-notification") != null;
491 * Starts the web interface and registers all toadlets.
493 public void start() {
496 /* notification templates. */
497 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
499 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
500 notificationManager.addNotification(startupNotification);
502 ticker.schedule(new Runnable() {
506 startupNotification.dismiss();
508 }, 2, TimeUnit.MINUTES);
510 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
511 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
512 ticker.scheduleAtFixedRate(new Runnable() {
515 @SuppressWarnings("synthetic-access")
517 if (getCore().getIdentityManager().isConnected()) {
518 wotMissingNotification.dismiss();
520 notificationManager.addNotification(wotMissingNotification);
524 }, 15, 15, TimeUnit.SECONDS);
528 * Stops the web interface and unregisters all toadlets.
531 pageToadletRegistry.unregisterToadlets();
532 ticker.shutdownNow();
540 * Register all toadlets.
542 private void registerToadlets() {
543 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
544 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
545 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
547 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
548 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
549 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
585 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
587 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
591 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
592 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
593 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
594 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
595 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
596 pageToadletRegistry.addPage(new GetImagePage(this));
597 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
598 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
599 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
600 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
601 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
602 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
603 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
604 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
605 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
606 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
607 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
608 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
609 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
610 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
611 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
612 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
613 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
614 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
615 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
616 pageToadletRegistry.addPage(new LikeAjaxPage(this));
617 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
618 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
619 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
620 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
621 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
622 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
623 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
625 pageToadletRegistry.registerToadlets();
629 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
630 * {@link SonePart}s in the given text (after parsing it using
631 * {@link SoneTextParser}).
635 * @return All mentioned local Sones
637 private Collection<Sone> getMentionedSones(String text) {
638 /* we need no context to find mentioned Sones. */
639 Set<Sone> mentionedSones = new HashSet<>();
640 for (Part part : soneTextParser.parse(text, null)) {
641 if (part instanceof SonePart) {
642 mentionedSones.add(((SonePart) part).getSone());
645 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
649 * Returns the Sone insert notification for the given Sone. If no
650 * notification for the given Sone exists, a new notification is created and
654 * The Sone to get the insert notification for
655 * @return The Sone insert notification
657 private TemplateNotification getSoneInsertNotification(Sone sone) {
658 synchronized (soneInsertNotifications) {
659 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
660 if (templateNotification == null) {
661 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
662 templateNotification.set("insertSone", sone);
663 soneInsertNotifications.put(sone, templateNotification);
665 return templateNotification;
669 private boolean localSoneMentionedInNewPostOrReply(Post post) {
670 if (!post.getSone().isLocal()) {
671 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
675 for (PostReply postReply : getCore().getReplies(post.getId())) {
676 if (postReply.getSone().isLocal()) {
679 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
691 * Notifies the web interface that a new {@link Sone} was found.
693 * @param newSoneFoundEvent
697 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
698 newSoneNotification.add(newSoneFoundEvent.getSone());
699 if (!hasFirstStartNotification()) {
700 notificationManager.addNotification(newSoneNotification);
705 * Notifies the web interface that a new {@link Post} was found.
707 * @param newPostFoundEvent
711 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
712 Post post = newPostFoundEvent.getPost();
713 boolean isLocal = post.getSone().isLocal();
715 localPostNotification.add(post);
717 newPostNotification.add(post);
719 if (!hasFirstStartNotification()) {
720 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
721 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
722 mentionNotification.add(post);
723 notificationManager.addNotification(mentionNotification);
726 getCore().markPostKnown(post);
731 * Notifies the web interface that a new {@link PostReply} was found.
733 * @param newPostReplyFoundEvent
737 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
738 PostReply reply = newPostReplyFoundEvent.getPostReply();
739 boolean isLocal = reply.getSone().isLocal();
741 localReplyNotification.add(reply);
743 newReplyNotification.add(reply);
745 if (!hasFirstStartNotification()) {
746 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
747 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
748 mentionNotification.add(reply.getPost().get());
749 notificationManager.addNotification(mentionNotification);
752 getCore().markReplyKnown(reply);
757 * Notifies the web interface that a {@link Sone} was marked as known.
759 * @param markSoneKnownEvent
763 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
764 newSoneNotification.remove(markSoneKnownEvent.getSone());
768 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
769 removePost(markPostKnownEvent.getPost());
773 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
774 removeReply(markPostReplyKnownEvent.getPostReply());
778 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
779 newSoneNotification.remove(soneRemovedEvent.getSone());
783 public void postRemoved(PostRemovedEvent postRemovedEvent) {
784 removePost(postRemovedEvent.getPost());
787 private void removePost(Post post) {
788 newPostNotification.remove(post);
789 localPostNotification.remove(post);
790 if (!localSoneMentionedInNewPostOrReply(post)) {
791 mentionNotification.remove(post);
796 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
797 removeReply(postReplyRemovedEvent.getPostReply());
800 private void removeReply(PostReply reply) {
801 newReplyNotification.remove(reply);
802 localReplyNotification.remove(reply);
803 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
804 mentionNotification.remove(reply.getPost().get());
809 * Notifies the web interface that a Sone was locked.
811 * @param soneLockedEvent
815 public void soneLocked(SoneLockedEvent soneLockedEvent) {
816 final Sone sone = soneLockedEvent.getSone();
817 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
820 @SuppressWarnings("synthetic-access")
822 lockedSonesNotification.add(sone);
823 notificationManager.addNotification(lockedSonesNotification);
825 }, 5, TimeUnit.MINUTES);
826 lockedSonesTickerObjects.put(sone, tickerObject);
830 * Notifies the web interface that a Sone was unlocked.
832 * @param soneUnlockedEvent
836 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
837 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
838 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
842 * Notifies the web interface that a {@link Sone} is being inserted.
844 * @param soneInsertingEvent
848 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
849 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
850 soneInsertNotification.set("soneStatus", "inserting");
851 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
852 notificationManager.addNotification(soneInsertNotification);
857 * Notifies the web interface that a {@link Sone} was inserted.
859 * @param soneInsertedEvent
863 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
864 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
865 soneInsertNotification.set("soneStatus", "inserted");
866 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
867 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
868 notificationManager.addNotification(soneInsertNotification);
873 * Notifies the web interface that a {@link Sone} insert was aborted.
875 * @param soneInsertAbortedEvent
879 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
880 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
881 soneInsertNotification.set("soneStatus", "insert-aborted");
882 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
883 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
884 notificationManager.addNotification(soneInsertNotification);
889 * Notifies the web interface that a new Sone version was found.
891 * @param updateFoundEvent
895 public void updateFound(UpdateFoundEvent updateFoundEvent) {
896 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
897 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
898 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
899 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
900 notificationManager.addNotification(newVersionNotification);
904 * Notifies the web interface that an image insert was started
906 * @param imageInsertStartedEvent
910 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
911 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
912 notificationManager.addNotification(insertingImagesNotification);
916 * Notifies the web interface that an {@link Image} insert was aborted.
918 * @param imageInsertAbortedEvent
922 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
923 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
927 * Notifies the web interface that an {@link Image} insert is finished.
929 * @param imageInsertFinishedEvent
933 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
934 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
935 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
936 notificationManager.addNotification(insertedImagesNotification);
940 * Notifies the web interface that an {@link Image} insert has failed.
942 * @param imageInsertFailedEvent
946 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
947 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
948 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
949 notificationManager.addNotification(imageInsertFailedNotification);
953 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
954 pageToadletRegistry.activateDebugMode();