2 * Sone - WebInterface.java - Copyright © 2010â2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.web;
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
29 import java.util.TimeZone;
30 import java.util.UUID;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.ScheduledExecutorService;
33 import java.util.concurrent.ScheduledFuture;
34 import java.util.concurrent.TimeUnit;
35 import java.util.logging.Logger;
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
38 import javax.inject.Named;
40 import net.pterodactylus.sone.core.Core;
41 import net.pterodactylus.sone.core.ElementLoader;
42 import net.pterodactylus.sone.core.event.*;
43 import net.pterodactylus.sone.data.Image;
44 import net.pterodactylus.sone.data.Post;
45 import net.pterodactylus.sone.data.PostReply;
46 import net.pterodactylus.sone.data.Sone;
47 import net.pterodactylus.sone.freenet.L10nFilter;
48 import net.pterodactylus.sone.freenet.Translation;
49 import net.pterodactylus.sone.main.Loaders;
50 import net.pterodactylus.sone.main.PluginHomepage;
51 import net.pterodactylus.sone.main.PluginVersion;
52 import net.pterodactylus.sone.main.PluginYear;
53 import net.pterodactylus.sone.main.SonePlugin;
54 import net.pterodactylus.sone.notify.ListNotification;
55 import net.pterodactylus.sone.notify.ListNotificationFilter;
56 import net.pterodactylus.sone.notify.PostVisibilityFilter;
57 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
58 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
59 import net.pterodactylus.sone.template.ParserFilter;
60 import net.pterodactylus.sone.template.RenderFilter;
61 import net.pterodactylus.sone.template.ShortenFilter;
62 import net.pterodactylus.sone.text.Part;
63 import net.pterodactylus.sone.text.SonePart;
64 import net.pterodactylus.sone.text.SoneTextParser;
65 import net.pterodactylus.sone.text.TimeTextConverter;
66 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
67 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
68 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
71 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
72 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
73 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
74 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
75 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
76 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
83 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
84 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
85 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
86 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
87 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
88 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
92 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
93 import net.pterodactylus.sone.web.page.FreenetRequest;
94 import net.pterodactylus.sone.web.page.TemplateRenderer;
95 import net.pterodactylus.sone.web.pages.*;
96 import net.pterodactylus.util.notify.Notification;
97 import net.pterodactylus.util.notify.NotificationManager;
98 import net.pterodactylus.util.notify.TemplateNotification;
99 import net.pterodactylus.util.template.Template;
100 import net.pterodactylus.util.template.TemplateContextFactory;
101 import net.pterodactylus.util.web.RedirectPage;
102 import net.pterodactylus.util.web.TemplatePage;
104 import freenet.clients.http.SessionManager;
105 import freenet.clients.http.SessionManager.Session;
106 import freenet.clients.http.ToadletContext;
108 import com.codahale.metrics.*;
109 import com.google.common.base.Optional;
110 import com.google.common.collect.Collections2;
111 import com.google.common.collect.ImmutableSet;
112 import com.google.common.eventbus.Subscribe;
113 import com.google.inject.Inject;
116 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
117 * references to l10n helpers.
119 public class WebInterface implements SessionProvider {
122 private static final Logger logger = getLogger(WebInterface.class.getName());
124 /** The loaders for templates, pages, and classpath providers. */
125 private final Loaders loaders;
127 /** The notification manager. */
128 private final NotificationManager notificationManager;
130 /** The Sone plugin. */
131 private final SonePlugin sonePlugin;
133 /** The form password. */
134 private final String formPassword;
136 /** The template context factory. */
137 private final TemplateContextFactory templateContextFactory;
138 private final TemplateRenderer templateRenderer;
140 /** The Sone text parser. */
141 private final SoneTextParser soneTextParser;
143 /** The parser filter. */
144 private final ParserFilter parserFilter;
145 private final ShortenFilter shortenFilter;
146 private final RenderFilter renderFilter;
148 private final ListNotificationFilter listNotificationFilter;
149 private final PostVisibilityFilter postVisibilityFilter;
150 private final ReplyVisibilityFilter replyVisibilityFilter;
152 private final ElementLoader elementLoader;
153 private final LinkedElementRenderFilter linkedElementRenderFilter;
154 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
155 private final L10nFilter l10nFilter;
157 private final PageToadletRegistry pageToadletRegistry;
158 private final MetricRegistry metricRegistry;
159 private final Translation translation;
161 /** The ânew postâ notification. */
162 private final ListNotification<Post> newPostNotification;
164 /** The ânew replyâ notification. */
165 private final ListNotification<PostReply> newReplyNotification;
167 /** The invisible âlocal postâ notification. */
168 private final ListNotification<Post> localPostNotification;
170 /** The invisible âlocal replyâ notification. */
171 private final ListNotification<PostReply> localReplyNotification;
173 /** The âyou have been mentionedâ notification. */
174 private final ListNotification<Post> mentionNotification;
176 /** Notifications for sone inserts. */
177 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
179 /** Sone locked notification ticker objects. */
180 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
182 /** The âSone lockedâ notification. */
183 private final ListNotification<Sone> lockedSonesNotification;
185 /** The ânew versionâ notification. */
186 private final TemplateNotification newVersionNotification;
188 /** The âinserting imagesâ notification. */
189 private final ListNotification<Image> insertingImagesNotification;
191 /** The âinserted imagesâ notification. */
192 private final ListNotification<Image> insertedImagesNotification;
194 /** The âimage insert failedâ notification. */
195 private final ListNotification<Image> imageInsertFailedNotification;
197 /** Scheduled executor for time-based notifications. */
198 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
201 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
202 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
203 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
204 TemplateRenderer templateRenderer,
205 ParserFilter parserFilter, ShortenFilter shortenFilter,
206 RenderFilter renderFilter,
207 LinkedElementRenderFilter linkedElementRenderFilter,
208 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
209 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification) {
210 this.sonePlugin = sonePlugin;
211 this.loaders = loaders;
212 this.listNotificationFilter = listNotificationFilter;
213 this.postVisibilityFilter = postVisibilityFilter;
214 this.replyVisibilityFilter = replyVisibilityFilter;
215 this.elementLoader = elementLoader;
216 this.templateRenderer = templateRenderer;
217 this.parserFilter = parserFilter;
218 this.shortenFilter = shortenFilter;
219 this.renderFilter = renderFilter;
220 this.linkedElementRenderFilter = linkedElementRenderFilter;
221 this.pageToadletRegistry = pageToadletRegistry;
222 this.metricRegistry = metricRegistry;
223 this.l10nFilter = l10nFilter;
224 this.translation = translation;
225 this.notificationManager = notificationManager;
226 this.newPostNotification = newPostNotification;
227 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
228 soneTextParser = new SoneTextParser(getCore(), getCore());
230 this.templateContextFactory = templateContextFactory;
231 templateContextFactory.addTemplateObject("webInterface", this);
232 templateContextFactory.addTemplateObject("formPassword", formPassword);
234 /* create notifications. */
235 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
236 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
238 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
239 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
241 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
242 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
244 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
245 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
247 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
248 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
250 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
251 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
253 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
254 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
256 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
257 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
259 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
260 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
268 * Returns the Sone core used by the Sone plugin.
270 * @return The Sone core
273 public Core getCore() {
274 return sonePlugin.core();
278 * Returns the template context factory of the web interface.
280 * @return The template context factory
282 public TemplateContextFactory getTemplateContextFactory() {
283 return templateContextFactory;
286 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
287 return getSessionManager().useSession(toadletContenxt);
290 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
291 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
292 if (session == null) {
293 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
298 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
299 Collection<Sone> localSones = getCore().getLocalSones();
300 if (localSones.size() == 1) {
301 return localSones.iterator().next();
303 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
306 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
307 Collection<Sone> localSones = getCore().getLocalSones();
308 if (localSones.size() == 1) {
309 return localSones.iterator().next();
311 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
315 * Returns the currently logged in Sone.
319 * @return The currently logged in Sone, or {@code null} if no Sone is
320 * currently logged in
322 private Sone getCurrentSone(Session session) {
323 if (session == null) {
326 String soneId = (String) session.getAttribute("Sone.CurrentSone");
327 if (soneId == null) {
330 return getCore().getLocalSone(soneId);
335 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
336 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
340 * Sets the currently logged in Sone.
342 * @param toadletContext
343 * The toadlet context
345 * The Sone to set as currently logged in
348 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
349 Session session = getOrCreateCurrentSession(toadletContext);
351 session.removeAttribute("Sone.CurrentSone");
353 session.setAttribute("Sone.CurrentSone", sone.getId());
358 * Returns the notification manager.
360 * @return The notification manager
362 public NotificationManager getNotifications() {
363 return notificationManager;
367 public Optional<Notification> getNotification(@Nonnull String notificationId) {
368 return Optional.fromNullable(notificationManager.getNotification(notificationId));
372 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
373 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
376 public Translation getTranslation() {
381 * Returns the session manager of the node.
383 * @return The nodeâs session manager
385 public SessionManager getSessionManager() {
386 return sonePlugin.pluginRespirator().getSessionManager("Sone");
390 * Returns the nodeâs form password.
392 * @return The form password
394 public String getFormPassword() {
399 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
400 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
401 .addAll(newPostNotification.getElements())
402 .addAll(localPostNotification.getElements())
404 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
408 * Returns the replies that have been announced as new in the
409 * {@link #newReplyNotification}.
411 * @return The new replies
413 public Set<PostReply> getNewReplies() {
414 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
418 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
419 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
420 .addAll(newReplyNotification.getElements())
421 .addAll(localReplyNotification.getElements())
423 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
427 * Sets whether the current start of the plugin is the first start. It is
428 * considered a first start if the configuration file does not exist.
431 * {@code true} if no configuration file existed when Sone was
432 * loaded, {@code false} otherwise
434 public void setFirstStart(boolean firstStart) {
436 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
437 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
438 notificationManager.addNotification(firstStartNotification);
443 * Sets whether Sone was started with a fresh configuration file.
446 * {@code true} if Sone was started with a fresh configuration,
447 * {@code false} if the existing configuration could be read
449 public void setNewConfig(boolean newConfig) {
450 if (newConfig && !hasFirstStartNotification()) {
451 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
452 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
453 notificationManager.addNotification(configNotReadNotification);
462 * Returns whether the first start notification is currently displayed.
464 * @return {@code true} if the first-start notification is currently
465 * displayed, {@code false} otherwise
467 private boolean hasFirstStartNotification() {
468 return notificationManager.getNotification("first-start-notification") != null;
476 * Starts the web interface and registers all toadlets.
478 public void start() {
481 /* notification templates. */
482 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
484 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
485 notificationManager.addNotification(startupNotification);
487 ticker.schedule(new Runnable() {
491 startupNotification.dismiss();
493 }, 2, TimeUnit.MINUTES);
495 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
496 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
497 ticker.scheduleAtFixedRate(new Runnable() {
500 @SuppressWarnings("synthetic-access")
502 if (getCore().getIdentityManager().isConnected()) {
503 wotMissingNotification.dismiss();
505 notificationManager.addNotification(wotMissingNotification);
509 }, 15, 15, TimeUnit.SECONDS);
513 * Stops the web interface and unregisters all toadlets.
516 pageToadletRegistry.unregisterToadlets();
517 ticker.shutdownNow();
525 * Register all toadlets.
527 private void registerToadlets() {
528 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
529 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
530 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
532 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
533 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
534 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
535 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
536 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
537 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
538 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
539 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
540 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
541 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
542 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
543 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
544 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
545 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
570 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
576 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
577 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
578 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
579 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
580 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
581 pageToadletRegistry.addPage(new GetImagePage(this));
582 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
583 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
584 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
585 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
586 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
587 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
588 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
589 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
590 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
591 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
592 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
593 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
594 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
595 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
596 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
597 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
598 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
599 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
600 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
601 pageToadletRegistry.addPage(new LikeAjaxPage(this));
602 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
603 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
604 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
605 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
606 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
607 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
608 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
610 pageToadletRegistry.registerToadlets();
614 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
615 * {@link SonePart}s in the given text (after parsing it using
616 * {@link SoneTextParser}).
620 * @return All mentioned local Sones
622 private Collection<Sone> getMentionedSones(String text) {
623 /* we need no context to find mentioned Sones. */
624 Set<Sone> mentionedSones = new HashSet<>();
625 for (Part part : soneTextParser.parse(text, null)) {
626 if (part instanceof SonePart) {
627 mentionedSones.add(((SonePart) part).getSone());
630 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
634 * Returns the Sone insert notification for the given Sone. If no
635 * notification for the given Sone exists, a new notification is created and
639 * The Sone to get the insert notification for
640 * @return The Sone insert notification
642 private TemplateNotification getSoneInsertNotification(Sone sone) {
643 synchronized (soneInsertNotifications) {
644 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
645 if (templateNotification == null) {
646 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
647 templateNotification.set("insertSone", sone);
648 soneInsertNotifications.put(sone, templateNotification);
650 return templateNotification;
654 private boolean localSoneMentionedInNewPostOrReply(Post post) {
655 if (!post.getSone().isLocal()) {
656 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
660 for (PostReply postReply : getCore().getReplies(post.getId())) {
661 if (postReply.getSone().isLocal()) {
664 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
676 * Notifies the web interface that a new {@link Post} was found.
678 * @param newPostFoundEvent
682 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
683 Post post = newPostFoundEvent.getPost();
684 boolean isLocal = post.getSone().isLocal();
686 localPostNotification.add(post);
688 if (!hasFirstStartNotification()) {
690 notificationManager.addNotification(localPostNotification);
692 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
693 mentionNotification.add(post);
694 notificationManager.addNotification(mentionNotification);
700 * Notifies the web interface that a new {@link PostReply} was found.
702 * @param newPostReplyFoundEvent
706 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
707 PostReply reply = newPostReplyFoundEvent.getPostReply();
708 boolean isLocal = reply.getSone().isLocal();
710 localReplyNotification.add(reply);
712 newReplyNotification.add(reply);
714 if (!hasFirstStartNotification()) {
715 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
716 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
717 mentionNotification.add(reply.getPost().get());
718 notificationManager.addNotification(mentionNotification);
721 getCore().markReplyKnown(reply);
726 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
727 removePost(markPostKnownEvent.getPost());
731 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
732 removeReply(markPostReplyKnownEvent.getPostReply());
736 public void postRemoved(PostRemovedEvent postRemovedEvent) {
737 removePost(postRemovedEvent.getPost());
740 private void removePost(Post post) {
741 newPostNotification.remove(post);
742 localPostNotification.remove(post);
743 if (!localSoneMentionedInNewPostOrReply(post)) {
744 mentionNotification.remove(post);
749 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
750 removeReply(postReplyRemovedEvent.getPostReply());
753 private void removeReply(PostReply reply) {
754 newReplyNotification.remove(reply);
755 localReplyNotification.remove(reply);
756 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
757 mentionNotification.remove(reply.getPost().get());
762 * Notifies the web interface that a Sone was locked.
764 * @param soneLockedEvent
768 public void soneLocked(SoneLockedEvent soneLockedEvent) {
769 final Sone sone = soneLockedEvent.getSone();
770 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
773 @SuppressWarnings("synthetic-access")
775 lockedSonesNotification.add(sone);
776 notificationManager.addNotification(lockedSonesNotification);
778 }, 5, TimeUnit.MINUTES);
779 lockedSonesTickerObjects.put(sone, tickerObject);
783 * Notifies the web interface that a Sone was unlocked.
785 * @param soneUnlockedEvent
789 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
790 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
791 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
795 * Notifies the web interface that a {@link Sone} is being inserted.
797 * @param soneInsertingEvent
801 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
802 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
803 soneInsertNotification.set("soneStatus", "inserting");
804 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
805 notificationManager.addNotification(soneInsertNotification);
810 * Notifies the web interface that a {@link Sone} was inserted.
812 * @param soneInsertedEvent
816 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
817 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
818 soneInsertNotification.set("soneStatus", "inserted");
819 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
820 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
821 notificationManager.addNotification(soneInsertNotification);
826 * Notifies the web interface that a {@link Sone} insert was aborted.
828 * @param soneInsertAbortedEvent
832 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
833 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
834 soneInsertNotification.set("soneStatus", "insert-aborted");
835 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
836 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
837 notificationManager.addNotification(soneInsertNotification);
842 * Notifies the web interface that a new Sone version was found.
844 * @param updateFoundEvent
848 public void updateFound(UpdateFoundEvent updateFoundEvent) {
849 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
850 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
851 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
852 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
853 notificationManager.addNotification(newVersionNotification);
857 * Notifies the web interface that an image insert was started
859 * @param imageInsertStartedEvent
863 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
864 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
865 notificationManager.addNotification(insertingImagesNotification);
869 * Notifies the web interface that an {@link Image} insert was aborted.
871 * @param imageInsertAbortedEvent
875 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
876 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
880 * Notifies the web interface that an {@link Image} insert is finished.
882 * @param imageInsertFinishedEvent
886 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
887 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
888 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
889 notificationManager.addNotification(insertedImagesNotification);
893 * Notifies the web interface that an {@link Image} insert has failed.
895 * @param imageInsertFailedEvent
899 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
900 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
901 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
902 notificationManager.addNotification(imageInsertFailedNotification);
906 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
907 pageToadletRegistry.activateDebugMode();