2 * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.web;
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
29 import java.util.TimeZone;
30 import java.util.UUID;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.ScheduledExecutorService;
33 import java.util.concurrent.ScheduledFuture;
34 import java.util.concurrent.TimeUnit;
35 import java.util.logging.Logger;
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
39 import net.pterodactylus.sone.core.Core;
40 import net.pterodactylus.sone.core.ElementLoader;
41 import net.pterodactylus.sone.core.event.*;
42 import net.pterodactylus.sone.data.Image;
43 import net.pterodactylus.sone.data.Post;
44 import net.pterodactylus.sone.data.PostReply;
45 import net.pterodactylus.sone.data.Sone;
46 import net.pterodactylus.sone.freenet.L10nFilter;
47 import net.pterodactylus.sone.freenet.Translation;
48 import net.pterodactylus.sone.main.Loaders;
49 import net.pterodactylus.sone.main.PluginHomepage;
50 import net.pterodactylus.sone.main.PluginVersion;
51 import net.pterodactylus.sone.main.PluginYear;
52 import net.pterodactylus.sone.main.SonePlugin;
53 import net.pterodactylus.sone.notify.ListNotification;
54 import net.pterodactylus.sone.notify.ListNotificationFilter;
55 import net.pterodactylus.sone.notify.PostVisibilityFilter;
56 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
57 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
58 import net.pterodactylus.sone.template.ParserFilter;
59 import net.pterodactylus.sone.template.RenderFilter;
60 import net.pterodactylus.sone.template.ShortenFilter;
61 import net.pterodactylus.sone.text.Part;
62 import net.pterodactylus.sone.text.SonePart;
63 import net.pterodactylus.sone.text.SoneTextParser;
64 import net.pterodactylus.sone.text.TimeTextConverter;
65 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
66 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
67 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
68 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
69 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
70 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
71 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
72 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
73 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
74 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
75 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
76 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
77 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
83 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
84 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
85 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
86 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
87 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
88 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
89 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
90 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
92 import net.pterodactylus.sone.web.page.FreenetRequest;
93 import net.pterodactylus.sone.web.page.TemplateRenderer;
94 import net.pterodactylus.sone.web.pages.*;
95 import net.pterodactylus.util.notify.Notification;
96 import net.pterodactylus.util.notify.NotificationManager;
97 import net.pterodactylus.util.notify.TemplateNotification;
98 import net.pterodactylus.util.template.Template;
99 import net.pterodactylus.util.template.TemplateContextFactory;
100 import net.pterodactylus.util.web.RedirectPage;
101 import net.pterodactylus.util.web.TemplatePage;
103 import freenet.clients.http.SessionManager;
104 import freenet.clients.http.SessionManager.Session;
105 import freenet.clients.http.ToadletContext;
107 import com.codahale.metrics.*;
108 import com.google.common.base.Optional;
109 import com.google.common.collect.Collections2;
110 import com.google.common.collect.ImmutableSet;
111 import com.google.common.eventbus.Subscribe;
112 import com.google.inject.Inject;
115 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
116 * references to l10n helpers.
118 public class WebInterface implements SessionProvider {
121 private static final Logger logger = getLogger(WebInterface.class.getName());
123 /** The loaders for templates, pages, and classpath providers. */
124 private final Loaders loaders;
126 /** The notification manager. */
127 private final NotificationManager notificationManager;
129 /** The Sone plugin. */
130 private final SonePlugin sonePlugin;
132 /** The form password. */
133 private final String formPassword;
135 /** The template context factory. */
136 private final TemplateContextFactory templateContextFactory;
137 private final TemplateRenderer templateRenderer;
139 /** The Sone text parser. */
140 private final SoneTextParser soneTextParser;
142 /** The parser filter. */
143 private final ParserFilter parserFilter;
144 private final ShortenFilter shortenFilter;
145 private final RenderFilter renderFilter;
147 private final ListNotificationFilter listNotificationFilter;
148 private final PostVisibilityFilter postVisibilityFilter;
149 private final ReplyVisibilityFilter replyVisibilityFilter;
151 private final ElementLoader elementLoader;
152 private final LinkedElementRenderFilter linkedElementRenderFilter;
153 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
154 private final L10nFilter l10nFilter;
156 private final PageToadletRegistry pageToadletRegistry;
157 private final MetricRegistry metricRegistry;
158 private final Translation translation;
160 /** The “new post” notification. */
161 private final ListNotification<Post> newPostNotification;
163 /** The “new reply” notification. */
164 private final ListNotification<PostReply> newReplyNotification;
166 /** The invisible “local post” notification. */
167 private final ListNotification<Post> localPostNotification;
169 /** The invisible “local reply” notification. */
170 private final ListNotification<PostReply> localReplyNotification;
172 /** The “you have been mentioned” notification. */
173 private final ListNotification<Post> mentionNotification;
175 /** Notifications for sone inserts. */
176 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
178 /** Sone locked notification ticker objects. */
179 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
181 /** The “Sone locked” notification. */
182 private final ListNotification<Sone> lockedSonesNotification;
184 /** The “new version” notification. */
185 private final TemplateNotification newVersionNotification;
187 /** The “inserting images” notification. */
188 private final ListNotification<Image> insertingImagesNotification;
190 /** The “inserted images” notification. */
191 private final ListNotification<Image> insertedImagesNotification;
193 /** The “image insert failed” notification. */
194 private final ListNotification<Image> imageInsertFailedNotification;
196 /** Scheduled executor for time-based notifications. */
197 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
200 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
201 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
202 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
203 TemplateRenderer templateRenderer,
204 ParserFilter parserFilter, ShortenFilter shortenFilter,
205 RenderFilter renderFilter,
206 LinkedElementRenderFilter linkedElementRenderFilter,
207 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
208 NotificationManager notificationManager) {
209 this.sonePlugin = sonePlugin;
210 this.loaders = loaders;
211 this.listNotificationFilter = listNotificationFilter;
212 this.postVisibilityFilter = postVisibilityFilter;
213 this.replyVisibilityFilter = replyVisibilityFilter;
214 this.elementLoader = elementLoader;
215 this.templateRenderer = templateRenderer;
216 this.parserFilter = parserFilter;
217 this.shortenFilter = shortenFilter;
218 this.renderFilter = renderFilter;
219 this.linkedElementRenderFilter = linkedElementRenderFilter;
220 this.pageToadletRegistry = pageToadletRegistry;
221 this.metricRegistry = metricRegistry;
222 this.l10nFilter = l10nFilter;
223 this.translation = translation;
224 this.notificationManager = notificationManager;
225 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
226 soneTextParser = new SoneTextParser(getCore(), getCore());
228 this.templateContextFactory = templateContextFactory;
229 templateContextFactory.addTemplateObject("webInterface", this);
230 templateContextFactory.addTemplateObject("formPassword", formPassword);
232 /* create notifications. */
233 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
234 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
236 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
237 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
239 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
240 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
242 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
243 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
245 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
246 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
248 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
249 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
251 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
252 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
254 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
255 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
257 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
258 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
260 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
261 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
269 * Returns the Sone core used by the Sone plugin.
271 * @return The Sone core
274 public Core getCore() {
275 return sonePlugin.core();
279 * Returns the template context factory of the web interface.
281 * @return The template context factory
283 public TemplateContextFactory getTemplateContextFactory() {
284 return templateContextFactory;
287 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
288 return getSessionManager().useSession(toadletContenxt);
291 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
292 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
293 if (session == null) {
294 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
299 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
300 Collection<Sone> localSones = getCore().getLocalSones();
301 if (localSones.size() == 1) {
302 return localSones.iterator().next();
304 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
307 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
308 Collection<Sone> localSones = getCore().getLocalSones();
309 if (localSones.size() == 1) {
310 return localSones.iterator().next();
312 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
316 * Returns the currently logged in Sone.
320 * @return The currently logged in Sone, or {@code null} if no Sone is
321 * currently logged in
323 private Sone getCurrentSone(Session session) {
324 if (session == null) {
327 String soneId = (String) session.getAttribute("Sone.CurrentSone");
328 if (soneId == null) {
331 return getCore().getLocalSone(soneId);
336 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
337 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
341 * Sets the currently logged in Sone.
343 * @param toadletContext
344 * The toadlet context
346 * The Sone to set as currently logged in
349 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
350 Session session = getOrCreateCurrentSession(toadletContext);
352 session.removeAttribute("Sone.CurrentSone");
354 session.setAttribute("Sone.CurrentSone", sone.getId());
359 * Returns the notification manager.
361 * @return The notification manager
363 public NotificationManager getNotifications() {
364 return notificationManager;
368 public Optional<Notification> getNotification(@Nonnull String notificationId) {
369 return Optional.fromNullable(notificationManager.getNotification(notificationId));
373 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
374 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
377 public Translation getTranslation() {
382 * Returns the session manager of the node.
384 * @return The node’s session manager
386 public SessionManager getSessionManager() {
387 return sonePlugin.pluginRespirator().getSessionManager("Sone");
391 * Returns the node’s form password.
393 * @return The form password
395 public String getFormPassword() {
400 * Returns the posts that have been announced as new in the
401 * {@link #newPostNotification}.
403 * @return The new posts
405 public Set<Post> getNewPosts() {
406 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
410 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
411 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
412 .addAll(newPostNotification.getElements())
413 .addAll(localPostNotification.getElements())
415 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
419 * Returns the replies that have been announced as new in the
420 * {@link #newReplyNotification}.
422 * @return The new replies
424 public Set<PostReply> getNewReplies() {
425 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
429 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
430 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
431 .addAll(newReplyNotification.getElements())
432 .addAll(localReplyNotification.getElements())
434 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
438 * Sets whether the current start of the plugin is the first start. It is
439 * considered a first start if the configuration file does not exist.
442 * {@code true} if no configuration file existed when Sone was
443 * loaded, {@code false} otherwise
445 public void setFirstStart(boolean firstStart) {
447 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
448 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
449 notificationManager.addNotification(firstStartNotification);
454 * Sets whether Sone was started with a fresh configuration file.
457 * {@code true} if Sone was started with a fresh configuration,
458 * {@code false} if the existing configuration could be read
460 public void setNewConfig(boolean newConfig) {
461 if (newConfig && !hasFirstStartNotification()) {
462 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
463 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
464 notificationManager.addNotification(configNotReadNotification);
473 * Returns whether the first start notification is currently displayed.
475 * @return {@code true} if the first-start notification is currently
476 * displayed, {@code false} otherwise
478 private boolean hasFirstStartNotification() {
479 return notificationManager.getNotification("first-start-notification") != null;
487 * Starts the web interface and registers all toadlets.
489 public void start() {
492 /* notification templates. */
493 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
495 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
496 notificationManager.addNotification(startupNotification);
498 ticker.schedule(new Runnable() {
502 startupNotification.dismiss();
504 }, 2, TimeUnit.MINUTES);
506 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
507 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
508 ticker.scheduleAtFixedRate(new Runnable() {
511 @SuppressWarnings("synthetic-access")
513 if (getCore().getIdentityManager().isConnected()) {
514 wotMissingNotification.dismiss();
516 notificationManager.addNotification(wotMissingNotification);
520 }, 15, 15, TimeUnit.SECONDS);
524 * Stops the web interface and unregisters all toadlets.
527 pageToadletRegistry.unregisterToadlets();
528 ticker.shutdownNow();
536 * Register all toadlets.
538 private void registerToadlets() {
539 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
540 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
541 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
543 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
544 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
545 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
546 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
547 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
548 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
549 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
550 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
551 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
552 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
553 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
554 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
555 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
556 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
557 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
581 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
587 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
588 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
589 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
590 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
591 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
592 pageToadletRegistry.addPage(new GetImagePage(this));
593 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
594 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
595 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
596 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
597 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
598 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
599 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
600 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
601 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
602 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
603 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
604 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
605 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
606 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
607 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
608 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
609 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
610 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
611 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
612 pageToadletRegistry.addPage(new LikeAjaxPage(this));
613 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
614 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
615 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
616 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
617 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
618 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
619 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
621 pageToadletRegistry.registerToadlets();
625 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
626 * {@link SonePart}s in the given text (after parsing it using
627 * {@link SoneTextParser}).
631 * @return All mentioned local Sones
633 private Collection<Sone> getMentionedSones(String text) {
634 /* we need no context to find mentioned Sones. */
635 Set<Sone> mentionedSones = new HashSet<>();
636 for (Part part : soneTextParser.parse(text, null)) {
637 if (part instanceof SonePart) {
638 mentionedSones.add(((SonePart) part).getSone());
641 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
645 * Returns the Sone insert notification for the given Sone. If no
646 * notification for the given Sone exists, a new notification is created and
650 * The Sone to get the insert notification for
651 * @return The Sone insert notification
653 private TemplateNotification getSoneInsertNotification(Sone sone) {
654 synchronized (soneInsertNotifications) {
655 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
656 if (templateNotification == null) {
657 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
658 templateNotification.set("insertSone", sone);
659 soneInsertNotifications.put(sone, templateNotification);
661 return templateNotification;
665 private boolean localSoneMentionedInNewPostOrReply(Post post) {
666 if (!post.getSone().isLocal()) {
667 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
671 for (PostReply postReply : getCore().getReplies(post.getId())) {
672 if (postReply.getSone().isLocal()) {
675 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
687 * Notifies the web interface that a new {@link Post} was found.
689 * @param newPostFoundEvent
693 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
694 Post post = newPostFoundEvent.getPost();
695 boolean isLocal = post.getSone().isLocal();
697 localPostNotification.add(post);
699 newPostNotification.add(post);
701 if (!hasFirstStartNotification()) {
702 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
703 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
704 mentionNotification.add(post);
705 notificationManager.addNotification(mentionNotification);
711 * Notifies the web interface that a new {@link PostReply} was found.
713 * @param newPostReplyFoundEvent
717 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
718 PostReply reply = newPostReplyFoundEvent.getPostReply();
719 boolean isLocal = reply.getSone().isLocal();
721 localReplyNotification.add(reply);
723 newReplyNotification.add(reply);
725 if (!hasFirstStartNotification()) {
726 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
727 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
728 mentionNotification.add(reply.getPost().get());
729 notificationManager.addNotification(mentionNotification);
732 getCore().markReplyKnown(reply);
737 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
738 removePost(markPostKnownEvent.getPost());
742 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
743 removeReply(markPostReplyKnownEvent.getPostReply());
747 public void postRemoved(PostRemovedEvent postRemovedEvent) {
748 removePost(postRemovedEvent.getPost());
751 private void removePost(Post post) {
752 newPostNotification.remove(post);
753 localPostNotification.remove(post);
754 if (!localSoneMentionedInNewPostOrReply(post)) {
755 mentionNotification.remove(post);
760 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
761 removeReply(postReplyRemovedEvent.getPostReply());
764 private void removeReply(PostReply reply) {
765 newReplyNotification.remove(reply);
766 localReplyNotification.remove(reply);
767 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
768 mentionNotification.remove(reply.getPost().get());
773 * Notifies the web interface that a Sone was locked.
775 * @param soneLockedEvent
779 public void soneLocked(SoneLockedEvent soneLockedEvent) {
780 final Sone sone = soneLockedEvent.getSone();
781 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
784 @SuppressWarnings("synthetic-access")
786 lockedSonesNotification.add(sone);
787 notificationManager.addNotification(lockedSonesNotification);
789 }, 5, TimeUnit.MINUTES);
790 lockedSonesTickerObjects.put(sone, tickerObject);
794 * Notifies the web interface that a Sone was unlocked.
796 * @param soneUnlockedEvent
800 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
801 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
802 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
806 * Notifies the web interface that a {@link Sone} is being inserted.
808 * @param soneInsertingEvent
812 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
813 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
814 soneInsertNotification.set("soneStatus", "inserting");
815 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
816 notificationManager.addNotification(soneInsertNotification);
821 * Notifies the web interface that a {@link Sone} was inserted.
823 * @param soneInsertedEvent
827 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
828 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
829 soneInsertNotification.set("soneStatus", "inserted");
830 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
831 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
832 notificationManager.addNotification(soneInsertNotification);
837 * Notifies the web interface that a {@link Sone} insert was aborted.
839 * @param soneInsertAbortedEvent
843 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
844 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
845 soneInsertNotification.set("soneStatus", "insert-aborted");
846 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
847 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
848 notificationManager.addNotification(soneInsertNotification);
853 * Notifies the web interface that a new Sone version was found.
855 * @param updateFoundEvent
859 public void updateFound(UpdateFoundEvent updateFoundEvent) {
860 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
861 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
862 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
863 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
864 notificationManager.addNotification(newVersionNotification);
868 * Notifies the web interface that an image insert was started
870 * @param imageInsertStartedEvent
874 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
875 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
876 notificationManager.addNotification(insertingImagesNotification);
880 * Notifies the web interface that an {@link Image} insert was aborted.
882 * @param imageInsertAbortedEvent
886 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
887 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
891 * Notifies the web interface that an {@link Image} insert is finished.
893 * @param imageInsertFinishedEvent
897 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
898 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
899 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
900 notificationManager.addNotification(insertedImagesNotification);
904 * Notifies the web interface that an {@link Image} insert has failed.
906 * @param imageInsertFailedEvent
910 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
911 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
912 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
913 notificationManager.addNotification(imageInsertFailedNotification);
917 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
918 pageToadletRegistry.activateDebugMode();