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.ImageInsertAbortedEvent;
44 import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
45 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
46 import net.pterodactylus.sone.core.event.ImageInsertStartedEvent;
47 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
48 import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
49 import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
50 import net.pterodactylus.sone.core.event.NewPostFoundEvent;
51 import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
52 import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
53 import net.pterodactylus.sone.core.event.PostRemovedEvent;
54 import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
55 import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
56 import net.pterodactylus.sone.core.event.SoneInsertedEvent;
57 import net.pterodactylus.sone.core.event.SoneInsertingEvent;
58 import net.pterodactylus.sone.core.event.SoneLockedEvent;
59 import net.pterodactylus.sone.core.event.SoneRemovedEvent;
60 import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
61 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
62 import net.pterodactylus.sone.data.Image;
63 import net.pterodactylus.sone.data.Post;
64 import net.pterodactylus.sone.data.PostReply;
65 import net.pterodactylus.sone.data.Sone;
66 import net.pterodactylus.sone.freenet.L10nFilter;
67 import net.pterodactylus.sone.main.Loaders;
68 import net.pterodactylus.sone.main.PluginHomepage;
69 import net.pterodactylus.sone.main.PluginVersion;
70 import net.pterodactylus.sone.main.PluginYear;
71 import net.pterodactylus.sone.main.SonePlugin;
72 import net.pterodactylus.sone.notify.ListNotification;
73 import net.pterodactylus.sone.notify.ListNotificationFilter;
74 import net.pterodactylus.sone.notify.PostVisibilityFilter;
75 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
76 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
77 import net.pterodactylus.sone.template.ParserFilter;
78 import net.pterodactylus.sone.template.RenderFilter;
79 import net.pterodactylus.sone.template.ShortenFilter;
80 import net.pterodactylus.sone.text.Part;
81 import net.pterodactylus.sone.text.SonePart;
82 import net.pterodactylus.sone.text.SoneTextParser;
83 import net.pterodactylus.sone.text.TimeTextConverter;
84 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
85 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
86 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
87 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
88 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
89 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
90 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
91 import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
92 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
93 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
94 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
95 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
96 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
97 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
98 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
99 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
100 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
101 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
102 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
103 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
104 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
105 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
106 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
107 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
108 import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
109 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
110 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
111 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
112 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
113 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
114 import net.pterodactylus.sone.web.page.FreenetRequest;
115 import net.pterodactylus.sone.web.page.TemplateRenderer;
116 import net.pterodactylus.sone.web.pages.*;
117 import net.pterodactylus.util.notify.Notification;
118 import net.pterodactylus.util.notify.NotificationManager;
119 import net.pterodactylus.util.notify.TemplateNotification;
120 import net.pterodactylus.util.template.Template;
121 import net.pterodactylus.util.template.TemplateContextFactory;
122 import net.pterodactylus.util.web.RedirectPage;
123 import net.pterodactylus.util.web.TemplatePage;
125 import freenet.clients.http.SessionManager;
126 import freenet.clients.http.SessionManager.Session;
127 import freenet.clients.http.ToadletContext;
128 import freenet.l10n.BaseL10n;
130 import com.codahale.metrics.*;
131 import com.google.common.base.Optional;
132 import com.google.common.collect.Collections2;
133 import com.google.common.collect.ImmutableSet;
134 import com.google.common.eventbus.Subscribe;
135 import com.google.inject.Inject;
138 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
139 * references to l10n helpers.
141 public class WebInterface implements SessionProvider {
144 private static final Logger logger = getLogger(WebInterface.class.getName());
146 /** The loaders for templates, pages, and classpath providers. */
147 private final Loaders loaders;
149 /** The notification manager. */
150 private final NotificationManager notificationManager = new NotificationManager();
152 /** The Sone plugin. */
153 private final SonePlugin sonePlugin;
155 /** The form password. */
156 private final String formPassword;
158 /** The template context factory. */
159 private final TemplateContextFactory templateContextFactory;
160 private final TemplateRenderer templateRenderer;
162 /** The Sone text parser. */
163 private final SoneTextParser soneTextParser;
165 /** The parser filter. */
166 private final ParserFilter parserFilter;
167 private final ShortenFilter shortenFilter;
168 private final RenderFilter renderFilter;
170 private final ListNotificationFilter listNotificationFilter;
171 private final PostVisibilityFilter postVisibilityFilter;
172 private final ReplyVisibilityFilter replyVisibilityFilter;
174 private final ElementLoader elementLoader;
175 private final LinkedElementRenderFilter linkedElementRenderFilter;
176 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
177 private final L10nFilter l10nFilter;
179 private final PageToadletRegistry pageToadletRegistry;
180 private final MetricRegistry metricRegistry;
182 /** The “new Sone” notification. */
183 private final ListNotification<Sone> newSoneNotification;
185 /** The “new post” notification. */
186 private final ListNotification<Post> newPostNotification;
188 /** The “new reply” notification. */
189 private final ListNotification<PostReply> newReplyNotification;
191 /** The invisible “local post” notification. */
192 private final ListNotification<Post> localPostNotification;
194 /** The invisible “local reply” notification. */
195 private final ListNotification<PostReply> localReplyNotification;
197 /** The “you have been mentioned” notification. */
198 private final ListNotification<Post> mentionNotification;
200 /** Notifications for sone inserts. */
201 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
203 /** Sone locked notification ticker objects. */
204 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
206 /** The “Sone locked” notification. */
207 private final ListNotification<Sone> lockedSonesNotification;
209 /** The “new version” notification. */
210 private final TemplateNotification newVersionNotification;
212 /** The “inserting images” notification. */
213 private final ListNotification<Image> insertingImagesNotification;
215 /** The “inserted images” notification. */
216 private final ListNotification<Image> insertedImagesNotification;
218 /** The “image insert failed” notification. */
219 private final ListNotification<Image> imageInsertFailedNotification;
221 /** Scheduled executor for time-based notifications. */
222 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
225 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
226 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
227 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
228 TemplateRenderer templateRenderer,
229 ParserFilter parserFilter, ShortenFilter shortenFilter,
230 RenderFilter renderFilter,
231 LinkedElementRenderFilter linkedElementRenderFilter,
232 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry) {
233 this.sonePlugin = sonePlugin;
234 this.loaders = loaders;
235 this.listNotificationFilter = listNotificationFilter;
236 this.postVisibilityFilter = postVisibilityFilter;
237 this.replyVisibilityFilter = replyVisibilityFilter;
238 this.elementLoader = elementLoader;
239 this.templateRenderer = templateRenderer;
240 this.parserFilter = parserFilter;
241 this.shortenFilter = shortenFilter;
242 this.renderFilter = renderFilter;
243 this.linkedElementRenderFilter = linkedElementRenderFilter;
244 this.pageToadletRegistry = pageToadletRegistry;
245 this.metricRegistry = metricRegistry;
246 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
247 soneTextParser = new SoneTextParser(getCore(), getCore());
248 l10nFilter = new L10nFilter(getL10n());
250 this.templateContextFactory = templateContextFactory;
251 templateContextFactory.addTemplateObject("webInterface", this);
252 templateContextFactory.addTemplateObject("formPassword", formPassword);
254 /* create notifications. */
255 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
256 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
258 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
259 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
261 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
262 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
264 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
265 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
267 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
268 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
270 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
271 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
273 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
274 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
276 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
277 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
279 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
280 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
282 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
283 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
285 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
286 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
294 * Returns the Sone core used by the Sone plugin.
296 * @return The Sone core
299 public Core getCore() {
300 return sonePlugin.core();
304 * Returns the template context factory of the web interface.
306 * @return The template context factory
308 public TemplateContextFactory getTemplateContextFactory() {
309 return templateContextFactory;
312 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
313 return getSessionManager().useSession(toadletContenxt);
316 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
317 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
318 if (session == null) {
319 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
324 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
325 Collection<Sone> localSones = getCore().getLocalSones();
326 if (localSones.size() == 1) {
327 return localSones.iterator().next();
329 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
332 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
333 Collection<Sone> localSones = getCore().getLocalSones();
334 if (localSones.size() == 1) {
335 return localSones.iterator().next();
337 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
341 * Returns the currently logged in Sone.
345 * @return The currently logged in Sone, or {@code null} if no Sone is
346 * currently logged in
348 private Sone getCurrentSone(Session session) {
349 if (session == null) {
352 String soneId = (String) session.getAttribute("Sone.CurrentSone");
353 if (soneId == null) {
356 return getCore().getLocalSone(soneId);
361 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
362 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
366 * Sets the currently logged in Sone.
368 * @param toadletContext
369 * The toadlet context
371 * The Sone to set as currently logged in
374 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
375 Session session = getOrCreateCurrentSession(toadletContext);
377 session.removeAttribute("Sone.CurrentSone");
379 session.setAttribute("Sone.CurrentSone", sone.getId());
384 * Returns the notification manager.
386 * @return The notification manager
388 public NotificationManager getNotifications() {
389 return notificationManager;
393 public Optional<Notification> getNotification(@Nonnull String notificationId) {
394 return Optional.fromNullable(notificationManager.getNotification(notificationId));
398 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
399 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
403 * Returns the l10n helper of the node.
405 * @return The node’s l10n helper
407 public BaseL10n getL10n() {
408 return sonePlugin.l10n().getBase();
412 * Returns the session manager of the node.
414 * @return The node’s session manager
416 public SessionManager getSessionManager() {
417 return sonePlugin.pluginRespirator().getSessionManager("Sone");
421 * Returns the node’s form password.
423 * @return The form password
425 public String getFormPassword() {
430 * Returns the posts that have been announced as new in the
431 * {@link #newPostNotification}.
433 * @return The new posts
435 public Set<Post> getNewPosts() {
436 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
440 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
441 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
442 .addAll(newPostNotification.getElements())
443 .addAll(localPostNotification.getElements())
445 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
449 * Returns the replies that have been announced as new in the
450 * {@link #newReplyNotification}.
452 * @return The new replies
454 public Set<PostReply> getNewReplies() {
455 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
459 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
460 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
461 .addAll(newReplyNotification.getElements())
462 .addAll(localReplyNotification.getElements())
464 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
468 * Sets whether the current start of the plugin is the first start. It is
469 * considered a first start if the configuration file does not exist.
472 * {@code true} if no configuration file existed when Sone was
473 * loaded, {@code false} otherwise
475 public void setFirstStart(boolean firstStart) {
477 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
478 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
479 notificationManager.addNotification(firstStartNotification);
484 * Sets whether Sone was started with a fresh configuration file.
487 * {@code true} if Sone was started with a fresh configuration,
488 * {@code false} if the existing configuration could be read
490 public void setNewConfig(boolean newConfig) {
491 if (newConfig && !hasFirstStartNotification()) {
492 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
493 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
494 notificationManager.addNotification(configNotReadNotification);
503 * Returns whether the first start notification is currently displayed.
505 * @return {@code true} if the first-start notification is currently
506 * displayed, {@code false} otherwise
508 private boolean hasFirstStartNotification() {
509 return notificationManager.getNotification("first-start-notification") != null;
517 * Starts the web interface and registers all toadlets.
519 public void start() {
522 /* notification templates. */
523 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
525 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
526 notificationManager.addNotification(startupNotification);
528 ticker.schedule(new Runnable() {
532 startupNotification.dismiss();
534 }, 2, TimeUnit.MINUTES);
536 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
537 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
538 ticker.scheduleAtFixedRate(new Runnable() {
541 @SuppressWarnings("synthetic-access")
543 if (getCore().getIdentityManager().isConnected()) {
544 wotMissingNotification.dismiss();
546 notificationManager.addNotification(wotMissingNotification);
550 }, 15, 15, TimeUnit.SECONDS);
554 * Stops the web interface and unregisters all toadlets.
557 pageToadletRegistry.unregisterToadlets();
558 ticker.shutdownNow();
566 * Register all toadlets.
568 private void registerToadlets() {
569 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
570 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
571 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
573 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
574 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
575 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
576 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
577 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
578 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
579 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
580 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
581 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
582 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
583 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
584 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
585 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
586 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
587 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
588 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
589 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
590 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
591 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
592 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
593 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
594 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
595 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
596 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
597 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
598 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
599 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
600 pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
601 pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
602 pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
603 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
604 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
605 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
606 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
607 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
608 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
609 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
610 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
611 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
612 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
613 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
614 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
615 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
616 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
617 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
618 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
619 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
620 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
621 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
622 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
623 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
624 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
625 pageToadletRegistry.addPage(new GetImagePage(this));
626 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
627 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
628 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
629 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
630 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
631 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
632 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
633 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
634 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
635 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
636 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
637 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
638 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
639 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
640 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
641 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
642 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
643 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
644 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
645 pageToadletRegistry.addPage(new TrustAjaxPage(this));
646 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
647 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
648 pageToadletRegistry.addPage(new LikeAjaxPage(this));
649 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
650 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
651 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
652 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
653 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
654 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
655 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
657 pageToadletRegistry.registerToadlets();
661 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
662 * {@link SonePart}s in the given text (after parsing it using
663 * {@link SoneTextParser}).
667 * @return All mentioned local Sones
669 private Collection<Sone> getMentionedSones(String text) {
670 /* we need no context to find mentioned Sones. */
671 Set<Sone> mentionedSones = new HashSet<>();
672 for (Part part : soneTextParser.parse(text, null)) {
673 if (part instanceof SonePart) {
674 mentionedSones.add(((SonePart) part).getSone());
677 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
681 * Returns the Sone insert notification for the given Sone. If no
682 * notification for the given Sone exists, a new notification is created and
686 * The Sone to get the insert notification for
687 * @return The Sone insert notification
689 private TemplateNotification getSoneInsertNotification(Sone sone) {
690 synchronized (soneInsertNotifications) {
691 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
692 if (templateNotification == null) {
693 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
694 templateNotification.set("insertSone", sone);
695 soneInsertNotifications.put(sone, templateNotification);
697 return templateNotification;
701 private boolean localSoneMentionedInNewPostOrReply(Post post) {
702 if (!post.getSone().isLocal()) {
703 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
707 for (PostReply postReply : getCore().getReplies(post.getId())) {
708 if (postReply.getSone().isLocal()) {
711 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
723 * Notifies the web interface that a new {@link Sone} was found.
725 * @param newSoneFoundEvent
729 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
730 newSoneNotification.add(newSoneFoundEvent.getSone());
731 if (!hasFirstStartNotification()) {
732 notificationManager.addNotification(newSoneNotification);
737 * Notifies the web interface that a new {@link Post} was found.
739 * @param newPostFoundEvent
743 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
744 Post post = newPostFoundEvent.getPost();
745 boolean isLocal = post.getSone().isLocal();
747 localPostNotification.add(post);
749 newPostNotification.add(post);
751 if (!hasFirstStartNotification()) {
752 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
753 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
754 mentionNotification.add(post);
755 notificationManager.addNotification(mentionNotification);
758 getCore().markPostKnown(post);
763 * Notifies the web interface that a new {@link PostReply} was found.
765 * @param newPostReplyFoundEvent
769 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
770 PostReply reply = newPostReplyFoundEvent.getPostReply();
771 boolean isLocal = reply.getSone().isLocal();
773 localReplyNotification.add(reply);
775 newReplyNotification.add(reply);
777 if (!hasFirstStartNotification()) {
778 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
779 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
780 mentionNotification.add(reply.getPost().get());
781 notificationManager.addNotification(mentionNotification);
784 getCore().markReplyKnown(reply);
789 * Notifies the web interface that a {@link Sone} was marked as known.
791 * @param markSoneKnownEvent
795 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
796 newSoneNotification.remove(markSoneKnownEvent.getSone());
800 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
801 removePost(markPostKnownEvent.getPost());
805 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
806 removeReply(markPostReplyKnownEvent.getPostReply());
810 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
811 newSoneNotification.remove(soneRemovedEvent.getSone());
815 public void postRemoved(PostRemovedEvent postRemovedEvent) {
816 removePost(postRemovedEvent.getPost());
819 private void removePost(Post post) {
820 newPostNotification.remove(post);
821 localPostNotification.remove(post);
822 if (!localSoneMentionedInNewPostOrReply(post)) {
823 mentionNotification.remove(post);
828 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
829 removeReply(postReplyRemovedEvent.getPostReply());
832 private void removeReply(PostReply reply) {
833 newReplyNotification.remove(reply);
834 localReplyNotification.remove(reply);
835 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
836 mentionNotification.remove(reply.getPost().get());
841 * Notifies the web interface that a Sone was locked.
843 * @param soneLockedEvent
847 public void soneLocked(SoneLockedEvent soneLockedEvent) {
848 final Sone sone = soneLockedEvent.getSone();
849 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
852 @SuppressWarnings("synthetic-access")
854 lockedSonesNotification.add(sone);
855 notificationManager.addNotification(lockedSonesNotification);
857 }, 5, TimeUnit.MINUTES);
858 lockedSonesTickerObjects.put(sone, tickerObject);
862 * Notifies the web interface that a Sone was unlocked.
864 * @param soneUnlockedEvent
868 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
869 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
870 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
874 * Notifies the web interface that a {@link Sone} is being inserted.
876 * @param soneInsertingEvent
880 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
881 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
882 soneInsertNotification.set("soneStatus", "inserting");
883 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
884 notificationManager.addNotification(soneInsertNotification);
889 * Notifies the web interface that a {@link Sone} was inserted.
891 * @param soneInsertedEvent
895 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
896 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
897 soneInsertNotification.set("soneStatus", "inserted");
898 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
899 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
900 notificationManager.addNotification(soneInsertNotification);
905 * Notifies the web interface that a {@link Sone} insert was aborted.
907 * @param soneInsertAbortedEvent
911 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
912 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
913 soneInsertNotification.set("soneStatus", "insert-aborted");
914 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
915 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
916 notificationManager.addNotification(soneInsertNotification);
921 * Notifies the web interface that a new Sone version was found.
923 * @param updateFoundEvent
927 public void updateFound(UpdateFoundEvent updateFoundEvent) {
928 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
929 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
930 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
931 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
932 notificationManager.addNotification(newVersionNotification);
936 * Notifies the web interface that an image insert was started
938 * @param imageInsertStartedEvent
942 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
943 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
944 notificationManager.addNotification(insertingImagesNotification);
948 * Notifies the web interface that an {@link Image} insert was aborted.
950 * @param imageInsertAbortedEvent
954 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
955 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
959 * Notifies the web interface that an {@link Image} insert is finished.
961 * @param imageInsertFinishedEvent
965 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
966 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
967 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
968 notificationManager.addNotification(insertedImagesNotification);
972 * Notifies the web interface that an {@link Image} insert has failed.
974 * @param imageInsertFailedEvent
978 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
979 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
980 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
981 notificationManager.addNotification(imageInsertFailedNotification);