027fb7f2283946ff6b4fc7b3334e42686693e5d3
[Sone.git] / src / main / java / net / pterodactylus / sone / web / WebInterface.java
1 /*
2  * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.sone.web;
19
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
22
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.TimeZone;
28 import java.util.UUID;
29 import java.util.logging.Logger;
30 import javax.annotation.Nonnull;
31 import javax.annotation.Nullable;
32 import javax.inject.Named;
33
34 import net.pterodactylus.sone.core.Core;
35 import net.pterodactylus.sone.core.ElementLoader;
36 import net.pterodactylus.sone.core.event.*;
37 import net.pterodactylus.sone.data.Post;
38 import net.pterodactylus.sone.data.PostReply;
39 import net.pterodactylus.sone.data.Sone;
40 import net.pterodactylus.sone.freenet.L10nFilter;
41 import net.pterodactylus.sone.freenet.Translation;
42 import net.pterodactylus.sone.main.Loaders;
43 import net.pterodactylus.sone.main.PluginHomepage;
44 import net.pterodactylus.sone.main.PluginVersion;
45 import net.pterodactylus.sone.main.PluginYear;
46 import net.pterodactylus.sone.main.SonePlugin;
47 import net.pterodactylus.sone.notify.ListNotification;
48 import net.pterodactylus.sone.notify.ListNotificationFilter;
49 import net.pterodactylus.sone.notify.PostVisibilityFilter;
50 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
51 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
52 import net.pterodactylus.sone.template.ParserFilter;
53 import net.pterodactylus.sone.template.RenderFilter;
54 import net.pterodactylus.sone.template.ShortenFilter;
55 import net.pterodactylus.sone.text.TimeTextConverter;
56 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
57 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
58 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
59 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
60 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
61 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
62 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
63 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
64 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
65 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
66 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
67 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
68 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
69 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
70 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
71 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
72 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
73 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
74 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
75 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
76 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
77 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
78 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
79 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
80 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
81 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
82 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
83 import net.pterodactylus.sone.web.page.FreenetRequest;
84 import net.pterodactylus.sone.web.page.TemplateRenderer;
85 import net.pterodactylus.sone.web.pages.*;
86 import net.pterodactylus.util.notify.Notification;
87 import net.pterodactylus.util.notify.NotificationManager;
88 import net.pterodactylus.util.notify.TemplateNotification;
89 import net.pterodactylus.util.template.Template;
90 import net.pterodactylus.util.template.TemplateContextFactory;
91 import net.pterodactylus.util.web.RedirectPage;
92 import net.pterodactylus.util.web.TemplatePage;
93
94 import freenet.clients.http.SessionManager;
95 import freenet.clients.http.SessionManager.Session;
96 import freenet.clients.http.ToadletContext;
97
98 import com.codahale.metrics.*;
99 import com.google.common.base.Optional;
100 import com.google.common.collect.ImmutableSet;
101 import com.google.common.eventbus.Subscribe;
102 import com.google.inject.Inject;
103
104 /**
105  * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
106  * references to l10n helpers.
107  */
108 public class WebInterface implements SessionProvider {
109
110         /** The logger. */
111         private static final Logger logger = getLogger(WebInterface.class.getName());
112
113         /** The loaders for templates, pages, and classpath providers. */
114         private final Loaders loaders;
115
116         /** The notification manager. */
117         private final NotificationManager notificationManager;
118
119         /** The Sone plugin. */
120         private final SonePlugin sonePlugin;
121
122         /** The form password. */
123         private final String formPassword;
124
125         /** The template context factory. */
126         private final TemplateContextFactory templateContextFactory;
127         private final TemplateRenderer templateRenderer;
128
129         /** The parser filter. */
130         private final ParserFilter parserFilter;
131         private final ShortenFilter shortenFilter;
132         private final RenderFilter renderFilter;
133
134         private final ListNotificationFilter listNotificationFilter;
135         private final PostVisibilityFilter postVisibilityFilter;
136         private final ReplyVisibilityFilter replyVisibilityFilter;
137
138         private final ElementLoader elementLoader;
139         private final LinkedElementRenderFilter linkedElementRenderFilter;
140         private final TimeTextConverter timeTextConverter = new TimeTextConverter();
141         private final L10nFilter l10nFilter;
142
143         private final PageToadletRegistry pageToadletRegistry;
144         private final MetricRegistry metricRegistry;
145         private final Translation translation;
146
147         /** The “new post” notification. */
148         private final ListNotification<Post> newPostNotification;
149
150         /** The “new reply” notification. */
151         private final ListNotification<PostReply> newReplyNotification;
152
153         /** The invisible “local post” notification. */
154         private final ListNotification<Post> localPostNotification;
155
156         /** The invisible “local reply” notification. */
157         private final ListNotification<PostReply> localReplyNotification;
158
159         /** Notifications for sone inserts. */
160         private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
161
162         @Inject
163         public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
164                         PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
165                         ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
166                         TemplateRenderer templateRenderer,
167                         ParserFilter parserFilter, ShortenFilter shortenFilter,
168                         RenderFilter renderFilter,
169                         LinkedElementRenderFilter linkedElementRenderFilter,
170                         PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
171                         NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
172                         @Named("newRemotePostReply") ListNotification<PostReply> newReplyNotification,
173                         @Named("localPost") ListNotification<Post> localPostNotification,
174                         @Named("localReply") ListNotification<PostReply> localReplyNotification) {
175                 this.sonePlugin = sonePlugin;
176                 this.loaders = loaders;
177                 this.listNotificationFilter = listNotificationFilter;
178                 this.postVisibilityFilter = postVisibilityFilter;
179                 this.replyVisibilityFilter = replyVisibilityFilter;
180                 this.elementLoader = elementLoader;
181                 this.templateRenderer = templateRenderer;
182                 this.parserFilter = parserFilter;
183                 this.shortenFilter = shortenFilter;
184                 this.renderFilter = renderFilter;
185                 this.linkedElementRenderFilter = linkedElementRenderFilter;
186                 this.pageToadletRegistry = pageToadletRegistry;
187                 this.metricRegistry = metricRegistry;
188                 this.l10nFilter = l10nFilter;
189                 this.translation = translation;
190                 this.notificationManager = notificationManager;
191                 this.newPostNotification = newPostNotification;
192                 this.newReplyNotification = newReplyNotification;
193                 this.localPostNotification = localPostNotification;
194                 this.localReplyNotification = localReplyNotification;
195                 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
196
197                 this.templateContextFactory = templateContextFactory;
198                 templateContextFactory.addTemplateObject("webInterface", this);
199                 templateContextFactory.addTemplateObject("formPassword", formPassword);
200         }
201
202         //
203         // ACCESSORS
204         //
205
206         /**
207          * Returns the Sone core used by the Sone plugin.
208          *
209          * @return The Sone core
210          */
211         @Nonnull
212         public Core getCore() {
213                 return sonePlugin.core();
214         }
215
216         /**
217          * Returns the template context factory of the web interface.
218          *
219          * @return The template context factory
220          */
221         public TemplateContextFactory getTemplateContextFactory() {
222                 return templateContextFactory;
223         }
224
225         private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
226                 return getSessionManager().useSession(toadletContenxt);
227         }
228
229         private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
230                 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
231                 if (session == null) {
232                         session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
233                 }
234                 return session;
235         }
236
237         public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
238                 Collection<Sone> localSones = getCore().getLocalSones();
239                 if (localSones.size() == 1) {
240                         return localSones.iterator().next();
241                 }
242                 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
243         }
244
245         public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
246                 Collection<Sone> localSones = getCore().getLocalSones();
247                 if (localSones.size() == 1) {
248                         return localSones.iterator().next();
249                 }
250                 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
251         }
252
253         /**
254          * Returns the currently logged in Sone.
255          *
256          * @param session
257          *            The session
258          * @return The currently logged in Sone, or {@code null} if no Sone is
259          *         currently logged in
260          */
261         private Sone getCurrentSone(Session session) {
262                 if (session == null) {
263                         return null;
264                 }
265                 String soneId = (String) session.getAttribute("Sone.CurrentSone");
266                 if (soneId == null) {
267                         return null;
268                 }
269                 return getCore().getLocalSone(soneId);
270         }
271
272         @Override
273         @Nullable
274         public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
275                 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
276         }
277
278         /**
279          * Sets the currently logged in Sone.
280          *
281          * @param toadletContext
282          *            The toadlet context
283          * @param sone
284          *            The Sone to set as currently logged in
285          */
286         @Override
287         public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
288                 Session session = getOrCreateCurrentSession(toadletContext);
289                 if (sone == null) {
290                         session.removeAttribute("Sone.CurrentSone");
291                 } else {
292                         session.setAttribute("Sone.CurrentSone", sone.getId());
293                 }
294         }
295
296         /**
297          * Returns the notification manager.
298          *
299          * @return The notification manager
300          */
301         public NotificationManager getNotifications() {
302                 return notificationManager;
303         }
304
305         @Nonnull
306         public Optional<Notification> getNotification(@Nonnull String notificationId) {
307                 return Optional.fromNullable(notificationManager.getNotification(notificationId));
308         }
309
310         @Nonnull
311         public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
312                 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
313         }
314
315         public Translation getTranslation() {
316                 return translation;
317         }
318
319         /**
320          * Returns the session manager of the node.
321          *
322          * @return The node’s session manager
323          */
324         public SessionManager getSessionManager() {
325                 return sonePlugin.pluginRespirator().getSessionManager("Sone");
326         }
327
328         /**
329          * Returns the node’s form password.
330          *
331          * @return The form password
332          */
333         public String getFormPassword() {
334                 return formPassword;
335         }
336
337         @Nonnull
338         public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
339                 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
340                                 .addAll(newPostNotification.getElements())
341                                 .addAll(localPostNotification.getElements())
342                                 .build();
343                 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
344         }
345
346         /**
347          * Returns the replies that have been announced as new in the
348          * {@link #newReplyNotification}.
349          *
350          * @return The new replies
351          */
352         public Set<PostReply> getNewReplies() {
353                 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
354         }
355
356         @Nonnull
357         public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
358                 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
359                                 .addAll(newReplyNotification.getElements())
360                                 .addAll(localReplyNotification.getElements())
361                                 .build();
362                 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
363         }
364
365         //
366         // PRIVATE ACCESSORS
367         //
368
369         /**
370          * Returns whether the first start notification is currently displayed.
371          *
372          * @return {@code true} if the first-start notification is currently
373          *         displayed, {@code false} otherwise
374          */
375         private boolean hasFirstStartNotification() {
376                 return notificationManager.getNotification("first-start-notification") != null;
377         }
378
379         //
380         // ACTIONS
381         //
382
383         /**
384          * Starts the web interface and registers all toadlets.
385          */
386         public void start() {
387                 registerToadlets();
388         }
389
390         /**
391          * Stops the web interface and unregisters all toadlets.
392          */
393         public void stop() {
394                 pageToadletRegistry.unregisterToadlets();
395         }
396
397         //
398         // PRIVATE METHODS
399         //
400
401         /**
402          * Register all toadlets.
403          */
404         private void registerToadlets() {
405                 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
406                 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
407                 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
408
409                 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
410                 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
411                 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
412                 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
413                 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
414                 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
415                 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
416                 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
417                 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
418                 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
419                 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
420                 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
421                 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
422                 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
423                 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
424                 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
425                 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
426                 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
427                 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
428                 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
429                 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
430                 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
431                 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
432                 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
433                 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
434                 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
435                 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
436                 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
437                 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
438                 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
439                 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
440                 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
441                 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
442                 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
443                 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
444                 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
445                 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
446                 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
447                 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
448                 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
449                 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
450                 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
451                 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
452                 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
453                 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
454                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
455                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
456                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
457                 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
458                 pageToadletRegistry.addPage(new GetImagePage(this));
459                 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
460                 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
461                 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
462                 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
463                 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
464                 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
465                 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
466                 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
467                 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
468                 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
469                 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
470                 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
471                 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
472                 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
473                 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
474                 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
475                 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
476                 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
477                 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
478                 pageToadletRegistry.addPage(new LikeAjaxPage(this));
479                 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
480                 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
481                 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
482                 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
483                 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
484                 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
485                 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
486
487                 pageToadletRegistry.registerToadlets();
488         }
489
490         /**
491          * Returns the Sone insert notification for the given Sone. If no
492          * notification for the given Sone exists, a new notification is created and
493          * cached.
494          *
495          * @param sone
496          *            The Sone to get the insert notification for
497          * @return The Sone insert notification
498          */
499         private TemplateNotification getSoneInsertNotification(Sone sone) {
500                 synchronized (soneInsertNotifications) {
501                         TemplateNotification templateNotification = soneInsertNotifications.get(sone);
502                         if (templateNotification == null) {
503                                 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
504                                 templateNotification.set("insertSone", sone);
505                                 soneInsertNotifications.put(sone, templateNotification);
506                         }
507                         return templateNotification;
508                 }
509         }
510
511         //
512         // EVENT HANDLERS
513         //
514
515         @Subscribe
516         public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
517                 removePost(markPostKnownEvent.getPost());
518         }
519
520         @Subscribe
521         public void postRemoved(PostRemovedEvent postRemovedEvent) {
522                 removePost(postRemovedEvent.getPost());
523         }
524
525         private void removePost(Post post) {
526                 newPostNotification.remove(post);
527         }
528
529         /**
530          * Notifies the web interface that a {@link Sone} is being inserted.
531          *
532          * @param soneInsertingEvent
533          *            The event
534          */
535         @Subscribe
536         public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
537                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
538                 soneInsertNotification.set("soneStatus", "inserting");
539                 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
540                         notificationManager.addNotification(soneInsertNotification);
541                 }
542         }
543
544         /**
545          * Notifies the web interface that a {@link Sone} was inserted.
546          *
547          * @param soneInsertedEvent
548          *            The event
549          */
550         @Subscribe
551         public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
552                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
553                 soneInsertNotification.set("soneStatus", "inserted");
554                 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
555                 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
556                         notificationManager.addNotification(soneInsertNotification);
557                 }
558         }
559
560         /**
561          * Notifies the web interface that a {@link Sone} insert was aborted.
562          *
563          * @param soneInsertAbortedEvent
564          *            The event
565          */
566         @Subscribe
567         public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
568                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
569                 soneInsertNotification.set("soneStatus", "insert-aborted");
570                 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
571                 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
572                         notificationManager.addNotification(soneInsertNotification);
573                 }
574         }
575
576         @Subscribe
577         public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
578                 pageToadletRegistry.activateDebugMode();
579         }
580
581 }