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;
22 import static net.pterodactylus.util.template.TemplateParser.parse;
24 import java.io.StringReader;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
31 import java.util.TimeZone;
32 import java.util.UUID;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.ScheduledFuture;
36 import java.util.concurrent.TimeUnit;
37 import java.util.logging.Logger;
38 import javax.annotation.Nonnull;
39 import javax.annotation.Nullable;
41 import net.pterodactylus.sone.core.Core;
42 import net.pterodactylus.sone.core.ElementLoader;
43 import net.pterodactylus.sone.core.event.*;
44 import net.pterodactylus.sone.data.Image;
45 import net.pterodactylus.sone.data.Post;
46 import net.pterodactylus.sone.data.PostReply;
47 import net.pterodactylus.sone.data.Sone;
48 import net.pterodactylus.sone.freenet.L10nFilter;
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.DistrustAjaxPage;
74 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
75 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
76 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
77 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
78 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
79 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
80 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
81 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
82 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
83 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
84 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
85 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
86 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
87 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
88 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
89 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
90 import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
91 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
92 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
93 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
94 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
95 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
96 import net.pterodactylus.sone.web.page.FreenetRequest;
97 import net.pterodactylus.sone.web.page.TemplateRenderer;
98 import net.pterodactylus.sone.web.pages.*;
99 import net.pterodactylus.util.notify.Notification;
100 import net.pterodactylus.util.notify.NotificationManager;
101 import net.pterodactylus.util.notify.TemplateNotification;
102 import net.pterodactylus.util.template.Template;
103 import net.pterodactylus.util.template.TemplateContextFactory;
104 import net.pterodactylus.util.web.RedirectPage;
105 import net.pterodactylus.util.web.TemplatePage;
107 import freenet.clients.http.SessionManager;
108 import freenet.clients.http.SessionManager.Session;
109 import freenet.clients.http.ToadletContext;
110 import freenet.l10n.BaseL10n;
112 import com.codahale.metrics.*;
113 import com.google.common.base.Optional;
114 import com.google.common.collect.Collections2;
115 import com.google.common.collect.ImmutableSet;
116 import com.google.common.eventbus.Subscribe;
117 import com.google.inject.Inject;
120 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
121 * references to l10n helpers.
123 public class WebInterface implements SessionProvider {
126 private static final Logger logger = getLogger(WebInterface.class.getName());
128 /** The loaders for templates, pages, and classpath providers. */
129 private final Loaders loaders;
131 /** The notification manager. */
132 private final NotificationManager notificationManager = new NotificationManager();
134 /** The Sone plugin. */
135 private final SonePlugin sonePlugin;
137 /** The form password. */
138 private final String formPassword;
140 /** The template context factory. */
141 private final TemplateContextFactory templateContextFactory;
142 private final TemplateRenderer templateRenderer;
144 /** The Sone text parser. */
145 private final SoneTextParser soneTextParser;
147 /** The parser filter. */
148 private final ParserFilter parserFilter;
149 private final ShortenFilter shortenFilter;
150 private final RenderFilter renderFilter;
152 private final ListNotificationFilter listNotificationFilter;
153 private final PostVisibilityFilter postVisibilityFilter;
154 private final ReplyVisibilityFilter replyVisibilityFilter;
156 private final ElementLoader elementLoader;
157 private final LinkedElementRenderFilter linkedElementRenderFilter;
158 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
159 private final L10nFilter l10nFilter;
161 private final PageToadletRegistry pageToadletRegistry;
162 private final MetricRegistry metricRegistry;
164 /** The “new Sone” notification. */
165 private final ListNotification<Sone> newSoneNotification;
167 /** The “new post” notification. */
168 private final ListNotification<Post> newPostNotification;
170 /** The “new reply” notification. */
171 private final ListNotification<PostReply> newReplyNotification;
173 /** The invisible “local post” notification. */
174 private final ListNotification<Post> localPostNotification;
176 /** The invisible “local reply” notification. */
177 private final ListNotification<PostReply> localReplyNotification;
179 /** The “you have been mentioned” notification. */
180 private final ListNotification<Post> mentionNotification;
182 /** Notifications for sone inserts. */
183 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
185 /** Sone locked notification ticker objects. */
186 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
188 /** The “Sone locked” notification. */
189 private final ListNotification<Sone> lockedSonesNotification;
191 /** The “new version” notification. */
192 private final TemplateNotification newVersionNotification;
194 /** The “inserting images” notification. */
195 private final ListNotification<Image> insertingImagesNotification;
197 /** The “inserted images” notification. */
198 private final ListNotification<Image> insertedImagesNotification;
200 /** The “image insert failed” notification. */
201 private final ListNotification<Image> imageInsertFailedNotification;
203 /** Scheduled executor for time-based notifications. */
204 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
207 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
208 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
209 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
210 TemplateRenderer templateRenderer,
211 ParserFilter parserFilter, ShortenFilter shortenFilter,
212 RenderFilter renderFilter,
213 LinkedElementRenderFilter linkedElementRenderFilter,
214 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry) {
215 this.sonePlugin = sonePlugin;
216 this.loaders = loaders;
217 this.listNotificationFilter = listNotificationFilter;
218 this.postVisibilityFilter = postVisibilityFilter;
219 this.replyVisibilityFilter = replyVisibilityFilter;
220 this.elementLoader = elementLoader;
221 this.templateRenderer = templateRenderer;
222 this.parserFilter = parserFilter;
223 this.shortenFilter = shortenFilter;
224 this.renderFilter = renderFilter;
225 this.linkedElementRenderFilter = linkedElementRenderFilter;
226 this.pageToadletRegistry = pageToadletRegistry;
227 this.metricRegistry = metricRegistry;
228 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
229 soneTextParser = new SoneTextParser(getCore(), getCore());
230 l10nFilter = new L10nFilter(getL10n());
232 this.templateContextFactory = templateContextFactory;
233 templateContextFactory.addTemplateObject("webInterface", this);
234 templateContextFactory.addTemplateObject("formPassword", formPassword);
236 /* create notifications. */
237 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
238 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
240 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
241 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
243 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
244 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
246 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
247 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
249 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
250 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
252 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
253 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
255 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
256 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
258 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
259 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
261 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
262 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
264 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
265 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
267 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
268 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
276 * Returns the Sone core used by the Sone plugin.
278 * @return The Sone core
281 public Core getCore() {
282 return sonePlugin.core();
286 * Returns the template context factory of the web interface.
288 * @return The template context factory
290 public TemplateContextFactory getTemplateContextFactory() {
291 return templateContextFactory;
294 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
295 return getSessionManager().useSession(toadletContenxt);
298 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
299 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
300 if (session == null) {
301 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
306 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
307 Collection<Sone> localSones = getCore().getLocalSones();
308 if (localSones.size() == 1) {
309 return localSones.iterator().next();
311 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
314 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
315 Collection<Sone> localSones = getCore().getLocalSones();
316 if (localSones.size() == 1) {
317 return localSones.iterator().next();
319 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
323 * Returns the currently logged in Sone.
327 * @return The currently logged in Sone, or {@code null} if no Sone is
328 * currently logged in
330 private Sone getCurrentSone(Session session) {
331 if (session == null) {
334 String soneId = (String) session.getAttribute("Sone.CurrentSone");
335 if (soneId == null) {
338 return getCore().getLocalSone(soneId);
343 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
344 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
348 * Sets the currently logged in Sone.
350 * @param toadletContext
351 * The toadlet context
353 * The Sone to set as currently logged in
356 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
357 Session session = getOrCreateCurrentSession(toadletContext);
359 session.removeAttribute("Sone.CurrentSone");
361 session.setAttribute("Sone.CurrentSone", sone.getId());
366 * Returns the notification manager.
368 * @return The notification manager
370 public NotificationManager getNotifications() {
371 return notificationManager;
375 public Optional<Notification> getNotification(@Nonnull String notificationId) {
376 return Optional.fromNullable(notificationManager.getNotification(notificationId));
380 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
381 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
385 * Returns the l10n helper of the node.
387 * @return The node’s l10n helper
389 public BaseL10n getL10n() {
390 return sonePlugin.l10n().getBase();
394 * Returns the session manager of the node.
396 * @return The node’s session manager
398 public SessionManager getSessionManager() {
399 return sonePlugin.pluginRespirator().getSessionManager("Sone");
403 * Returns the node’s form password.
405 * @return The form password
407 public String getFormPassword() {
412 * Returns the posts that have been announced as new in the
413 * {@link #newPostNotification}.
415 * @return The new posts
417 public Set<Post> getNewPosts() {
418 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
422 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
423 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
424 .addAll(newPostNotification.getElements())
425 .addAll(localPostNotification.getElements())
427 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
431 * Returns the replies that have been announced as new in the
432 * {@link #newReplyNotification}.
434 * @return The new replies
436 public Set<PostReply> getNewReplies() {
437 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
441 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
442 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
443 .addAll(newReplyNotification.getElements())
444 .addAll(localReplyNotification.getElements())
446 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
450 * Sets whether the current start of the plugin is the first start. It is
451 * considered a first start if the configuration file does not exist.
454 * {@code true} if no configuration file existed when Sone was
455 * loaded, {@code false} otherwise
457 public void setFirstStart(boolean firstStart) {
459 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
460 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
461 notificationManager.addNotification(firstStartNotification);
466 * Sets whether Sone was started with a fresh configuration file.
469 * {@code true} if Sone was started with a fresh configuration,
470 * {@code false} if the existing configuration could be read
472 public void setNewConfig(boolean newConfig) {
473 if (newConfig && !hasFirstStartNotification()) {
474 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
475 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
476 notificationManager.addNotification(configNotReadNotification);
485 * Returns whether the first start notification is currently displayed.
487 * @return {@code true} if the first-start notification is currently
488 * displayed, {@code false} otherwise
490 private boolean hasFirstStartNotification() {
491 return notificationManager.getNotification("first-start-notification") != null;
499 * Starts the web interface and registers all toadlets.
501 public void start() {
504 /* notification templates. */
505 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
507 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
508 notificationManager.addNotification(startupNotification);
510 ticker.schedule(new Runnable() {
514 startupNotification.dismiss();
516 }, 2, TimeUnit.MINUTES);
518 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
519 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
520 ticker.scheduleAtFixedRate(new Runnable() {
523 @SuppressWarnings("synthetic-access")
525 if (getCore().getIdentityManager().isConnected()) {
526 wotMissingNotification.dismiss();
528 notificationManager.addNotification(wotMissingNotification);
532 }, 15, 15, TimeUnit.SECONDS);
536 * Stops the web interface and unregisters all toadlets.
539 pageToadletRegistry.unregisterToadlets();
540 ticker.shutdownNow();
548 * Register all toadlets.
550 private void registerToadlets() {
551 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
552 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
553 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
555 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
556 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
557 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
558 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
559 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
560 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
561 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
562 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
563 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
564 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
565 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
566 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
567 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
568 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
569 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
570 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
571 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
572 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
573 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
574 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
575 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
587 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
591 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
592 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
593 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
594 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
595 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
596 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
597 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
598 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
599 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
600 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
601 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
602 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
603 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
604 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
605 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
606 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
607 pageToadletRegistry.addPage(new GetImagePage(this));
608 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
609 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
610 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
611 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
612 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
613 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
614 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
615 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
616 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
617 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
618 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
619 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
620 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
621 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
622 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
623 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
624 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
625 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
626 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
627 pageToadletRegistry.addPage(new TrustAjaxPage(this));
628 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
629 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
630 pageToadletRegistry.addPage(new LikeAjaxPage(this));
631 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
632 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
633 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
634 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
635 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
636 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
637 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
639 pageToadletRegistry.registerToadlets();
643 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
644 * {@link SonePart}s in the given text (after parsing it using
645 * {@link SoneTextParser}).
649 * @return All mentioned local Sones
651 private Collection<Sone> getMentionedSones(String text) {
652 /* we need no context to find mentioned Sones. */
653 Set<Sone> mentionedSones = new HashSet<>();
654 for (Part part : soneTextParser.parse(text, null)) {
655 if (part instanceof SonePart) {
656 mentionedSones.add(((SonePart) part).getSone());
659 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
663 * Returns the Sone insert notification for the given Sone. If no
664 * notification for the given Sone exists, a new notification is created and
668 * The Sone to get the insert notification for
669 * @return The Sone insert notification
671 private TemplateNotification getSoneInsertNotification(Sone sone) {
672 synchronized (soneInsertNotifications) {
673 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
674 if (templateNotification == null) {
675 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
676 templateNotification.set("insertSone", sone);
677 soneInsertNotifications.put(sone, templateNotification);
679 return templateNotification;
683 private boolean localSoneMentionedInNewPostOrReply(Post post) {
684 if (!post.getSone().isLocal()) {
685 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
689 for (PostReply postReply : getCore().getReplies(post.getId())) {
690 if (postReply.getSone().isLocal()) {
693 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
705 * Notifies the web interface that a new {@link Sone} was found.
707 * @param newSoneFoundEvent
711 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
712 newSoneNotification.add(newSoneFoundEvent.getSone());
713 if (!hasFirstStartNotification()) {
714 notificationManager.addNotification(newSoneNotification);
719 * Notifies the web interface that a new {@link Post} was found.
721 * @param newPostFoundEvent
725 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
726 Post post = newPostFoundEvent.getPost();
727 boolean isLocal = post.getSone().isLocal();
729 localPostNotification.add(post);
731 newPostNotification.add(post);
733 if (!hasFirstStartNotification()) {
734 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
735 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
736 mentionNotification.add(post);
737 notificationManager.addNotification(mentionNotification);
740 getCore().markPostKnown(post);
745 * Notifies the web interface that a new {@link PostReply} was found.
747 * @param newPostReplyFoundEvent
751 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
752 PostReply reply = newPostReplyFoundEvent.getPostReply();
753 boolean isLocal = reply.getSone().isLocal();
755 localReplyNotification.add(reply);
757 newReplyNotification.add(reply);
759 if (!hasFirstStartNotification()) {
760 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
761 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
762 mentionNotification.add(reply.getPost().get());
763 notificationManager.addNotification(mentionNotification);
766 getCore().markReplyKnown(reply);
771 * Notifies the web interface that a {@link Sone} was marked as known.
773 * @param markSoneKnownEvent
777 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
778 newSoneNotification.remove(markSoneKnownEvent.getSone());
782 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
783 removePost(markPostKnownEvent.getPost());
787 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
788 removeReply(markPostReplyKnownEvent.getPostReply());
792 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
793 newSoneNotification.remove(soneRemovedEvent.getSone());
797 public void postRemoved(PostRemovedEvent postRemovedEvent) {
798 removePost(postRemovedEvent.getPost());
801 private void removePost(Post post) {
802 newPostNotification.remove(post);
803 localPostNotification.remove(post);
804 if (!localSoneMentionedInNewPostOrReply(post)) {
805 mentionNotification.remove(post);
810 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
811 removeReply(postReplyRemovedEvent.getPostReply());
814 private void removeReply(PostReply reply) {
815 newReplyNotification.remove(reply);
816 localReplyNotification.remove(reply);
817 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
818 mentionNotification.remove(reply.getPost().get());
823 * Notifies the web interface that a Sone was locked.
825 * @param soneLockedEvent
829 public void soneLocked(SoneLockedEvent soneLockedEvent) {
830 final Sone sone = soneLockedEvent.getSone();
831 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
834 @SuppressWarnings("synthetic-access")
836 lockedSonesNotification.add(sone);
837 notificationManager.addNotification(lockedSonesNotification);
839 }, 5, TimeUnit.MINUTES);
840 lockedSonesTickerObjects.put(sone, tickerObject);
844 * Notifies the web interface that a Sone was unlocked.
846 * @param soneUnlockedEvent
850 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
851 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
852 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
856 * Notifies the web interface that a {@link Sone} is being inserted.
858 * @param soneInsertingEvent
862 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
863 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
864 soneInsertNotification.set("soneStatus", "inserting");
865 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
866 notificationManager.addNotification(soneInsertNotification);
871 * Notifies the web interface that a {@link Sone} was inserted.
873 * @param soneInsertedEvent
877 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
878 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
879 soneInsertNotification.set("soneStatus", "inserted");
880 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
881 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
882 notificationManager.addNotification(soneInsertNotification);
887 * Notifies the web interface that a {@link Sone} insert was aborted.
889 * @param soneInsertAbortedEvent
893 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
894 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
895 soneInsertNotification.set("soneStatus", "insert-aborted");
896 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
897 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
898 notificationManager.addNotification(soneInsertNotification);
903 * Notifies the web interface that a new Sone version was found.
905 * @param updateFoundEvent
909 public void updateFound(UpdateFoundEvent updateFoundEvent) {
910 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
911 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
912 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
913 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
914 notificationManager.addNotification(newVersionNotification);
918 * Notifies the web interface that an image insert was started
920 * @param imageInsertStartedEvent
924 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
925 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
926 notificationManager.addNotification(insertingImagesNotification);
930 * Notifies the web interface that an {@link Image} insert was aborted.
932 * @param imageInsertAbortedEvent
936 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
937 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
941 * Notifies the web interface that an {@link Image} insert is finished.
943 * @param imageInsertFinishedEvent
947 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
948 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
949 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
950 notificationManager.addNotification(insertedImagesNotification);
954 * Notifies the web interface that an {@link Image} insert has failed.
956 * @param imageInsertFailedEvent
960 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
961 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
962 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
963 notificationManager.addNotification(imageInsertFailedNotification);
967 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
968 pageToadletRegistry.activateDebugMode();