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);
708 getCore().markPostKnown(post);
713 * Notifies the web interface that a new {@link PostReply} was found.
715 * @param newPostReplyFoundEvent
719 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
720 PostReply reply = newPostReplyFoundEvent.getPostReply();
721 boolean isLocal = reply.getSone().isLocal();
723 localReplyNotification.add(reply);
725 newReplyNotification.add(reply);
727 if (!hasFirstStartNotification()) {
728 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
729 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
730 mentionNotification.add(reply.getPost().get());
731 notificationManager.addNotification(mentionNotification);
734 getCore().markReplyKnown(reply);
739 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
740 removePost(markPostKnownEvent.getPost());
744 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
745 removeReply(markPostReplyKnownEvent.getPostReply());
749 public void postRemoved(PostRemovedEvent postRemovedEvent) {
750 removePost(postRemovedEvent.getPost());
753 private void removePost(Post post) {
754 newPostNotification.remove(post);
755 localPostNotification.remove(post);
756 if (!localSoneMentionedInNewPostOrReply(post)) {
757 mentionNotification.remove(post);
762 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
763 removeReply(postReplyRemovedEvent.getPostReply());
766 private void removeReply(PostReply reply) {
767 newReplyNotification.remove(reply);
768 localReplyNotification.remove(reply);
769 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
770 mentionNotification.remove(reply.getPost().get());
775 * Notifies the web interface that a Sone was locked.
777 * @param soneLockedEvent
781 public void soneLocked(SoneLockedEvent soneLockedEvent) {
782 final Sone sone = soneLockedEvent.getSone();
783 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
786 @SuppressWarnings("synthetic-access")
788 lockedSonesNotification.add(sone);
789 notificationManager.addNotification(lockedSonesNotification);
791 }, 5, TimeUnit.MINUTES);
792 lockedSonesTickerObjects.put(sone, tickerObject);
796 * Notifies the web interface that a Sone was unlocked.
798 * @param soneUnlockedEvent
802 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
803 lockedSonesNotification.remove(soneUnlockedEvent.getSone());
804 lockedSonesTickerObjects.remove(soneUnlockedEvent.getSone()).cancel(false);
808 * Notifies the web interface that a {@link Sone} is being inserted.
810 * @param soneInsertingEvent
814 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
815 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
816 soneInsertNotification.set("soneStatus", "inserting");
817 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
818 notificationManager.addNotification(soneInsertNotification);
823 * Notifies the web interface that a {@link Sone} was inserted.
825 * @param soneInsertedEvent
829 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
830 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
831 soneInsertNotification.set("soneStatus", "inserted");
832 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
833 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
834 notificationManager.addNotification(soneInsertNotification);
839 * Notifies the web interface that a {@link Sone} insert was aborted.
841 * @param soneInsertAbortedEvent
845 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
846 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
847 soneInsertNotification.set("soneStatus", "insert-aborted");
848 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
849 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
850 notificationManager.addNotification(soneInsertNotification);
855 * Notifies the web interface that a new Sone version was found.
857 * @param updateFoundEvent
861 public void updateFound(UpdateFoundEvent updateFoundEvent) {
862 newVersionNotification.set("latestVersion", updateFoundEvent.getVersion());
863 newVersionNotification.set("latestEdition", updateFoundEvent.getLatestEdition());
864 newVersionNotification.set("releaseTime", updateFoundEvent.getReleaseTime());
865 newVersionNotification.set("disruptive", updateFoundEvent.isDisruptive());
866 notificationManager.addNotification(newVersionNotification);
870 * Notifies the web interface that an image insert was started
872 * @param imageInsertStartedEvent
876 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
877 insertingImagesNotification.add(imageInsertStartedEvent.getImage());
878 notificationManager.addNotification(insertingImagesNotification);
882 * Notifies the web interface that an {@link Image} insert was aborted.
884 * @param imageInsertAbortedEvent
888 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
889 insertingImagesNotification.remove(imageInsertAbortedEvent.getImage());
893 * Notifies the web interface that an {@link Image} insert is finished.
895 * @param imageInsertFinishedEvent
899 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
900 insertingImagesNotification.remove(imageInsertFinishedEvent.getImage());
901 insertedImagesNotification.add(imageInsertFinishedEvent.getImage());
902 notificationManager.addNotification(insertedImagesNotification);
906 * Notifies the web interface that an {@link Image} insert has failed.
908 * @param imageInsertFailedEvent
912 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
913 insertingImagesNotification.remove(imageInsertFailedEvent.getImage());
914 imageInsertFailedNotification.add(imageInsertFailedEvent.getImage());
915 notificationManager.addNotification(imageInsertFailedNotification);
919 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
920 pageToadletRegistry.activateDebugMode();