🚧 Add handler for marking replies as known during first start
[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("localPost") ListNotification<Post> localPostNotification) {
173                 this.sonePlugin = sonePlugin;
174                 this.loaders = loaders;
175                 this.listNotificationFilter = listNotificationFilter;
176                 this.postVisibilityFilter = postVisibilityFilter;
177                 this.replyVisibilityFilter = replyVisibilityFilter;
178                 this.elementLoader = elementLoader;
179                 this.templateRenderer = templateRenderer;
180                 this.parserFilter = parserFilter;
181                 this.shortenFilter = shortenFilter;
182                 this.renderFilter = renderFilter;
183                 this.linkedElementRenderFilter = linkedElementRenderFilter;
184                 this.pageToadletRegistry = pageToadletRegistry;
185                 this.metricRegistry = metricRegistry;
186                 this.l10nFilter = l10nFilter;
187                 this.translation = translation;
188                 this.notificationManager = notificationManager;
189                 this.newPostNotification = newPostNotification;
190                 this.localPostNotification = localPostNotification;
191                 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
192
193                 this.templateContextFactory = templateContextFactory;
194                 templateContextFactory.addTemplateObject("webInterface", this);
195                 templateContextFactory.addTemplateObject("formPassword", formPassword);
196
197                 /* create notifications. */
198                 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
199                 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
200
201                 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
202                 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
203         }
204
205         //
206         // ACCESSORS
207         //
208
209         /**
210          * Returns the Sone core used by the Sone plugin.
211          *
212          * @return The Sone core
213          */
214         @Nonnull
215         public Core getCore() {
216                 return sonePlugin.core();
217         }
218
219         /**
220          * Returns the template context factory of the web interface.
221          *
222          * @return The template context factory
223          */
224         public TemplateContextFactory getTemplateContextFactory() {
225                 return templateContextFactory;
226         }
227
228         private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
229                 return getSessionManager().useSession(toadletContenxt);
230         }
231
232         private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
233                 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
234                 if (session == null) {
235                         session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
236                 }
237                 return session;
238         }
239
240         public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
241                 Collection<Sone> localSones = getCore().getLocalSones();
242                 if (localSones.size() == 1) {
243                         return localSones.iterator().next();
244                 }
245                 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
246         }
247
248         public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
249                 Collection<Sone> localSones = getCore().getLocalSones();
250                 if (localSones.size() == 1) {
251                         return localSones.iterator().next();
252                 }
253                 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
254         }
255
256         /**
257          * Returns the currently logged in Sone.
258          *
259          * @param session
260          *            The session
261          * @return The currently logged in Sone, or {@code null} if no Sone is
262          *         currently logged in
263          */
264         private Sone getCurrentSone(Session session) {
265                 if (session == null) {
266                         return null;
267                 }
268                 String soneId = (String) session.getAttribute("Sone.CurrentSone");
269                 if (soneId == null) {
270                         return null;
271                 }
272                 return getCore().getLocalSone(soneId);
273         }
274
275         @Override
276         @Nullable
277         public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
278                 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
279         }
280
281         /**
282          * Sets the currently logged in Sone.
283          *
284          * @param toadletContext
285          *            The toadlet context
286          * @param sone
287          *            The Sone to set as currently logged in
288          */
289         @Override
290         public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
291                 Session session = getOrCreateCurrentSession(toadletContext);
292                 if (sone == null) {
293                         session.removeAttribute("Sone.CurrentSone");
294                 } else {
295                         session.setAttribute("Sone.CurrentSone", sone.getId());
296                 }
297         }
298
299         /**
300          * Returns the notification manager.
301          *
302          * @return The notification manager
303          */
304         public NotificationManager getNotifications() {
305                 return notificationManager;
306         }
307
308         @Nonnull
309         public Optional<Notification> getNotification(@Nonnull String notificationId) {
310                 return Optional.fromNullable(notificationManager.getNotification(notificationId));
311         }
312
313         @Nonnull
314         public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
315                 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
316         }
317
318         public Translation getTranslation() {
319                 return translation;
320         }
321
322         /**
323          * Returns the session manager of the node.
324          *
325          * @return The node’s session manager
326          */
327         public SessionManager getSessionManager() {
328                 return sonePlugin.pluginRespirator().getSessionManager("Sone");
329         }
330
331         /**
332          * Returns the node’s form password.
333          *
334          * @return The form password
335          */
336         public String getFormPassword() {
337                 return formPassword;
338         }
339
340         @Nonnull
341         public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
342                 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
343                                 .addAll(newPostNotification.getElements())
344                                 .addAll(localPostNotification.getElements())
345                                 .build();
346                 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
347         }
348
349         /**
350          * Returns the replies that have been announced as new in the
351          * {@link #newReplyNotification}.
352          *
353          * @return The new replies
354          */
355         public Set<PostReply> getNewReplies() {
356                 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
357         }
358
359         @Nonnull
360         public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
361                 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
362                                 .addAll(newReplyNotification.getElements())
363                                 .addAll(localReplyNotification.getElements())
364                                 .build();
365                 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
366         }
367
368         //
369         // PRIVATE ACCESSORS
370         //
371
372         /**
373          * Returns whether the first start notification is currently displayed.
374          *
375          * @return {@code true} if the first-start notification is currently
376          *         displayed, {@code false} otherwise
377          */
378         private boolean hasFirstStartNotification() {
379                 return notificationManager.getNotification("first-start-notification") != null;
380         }
381
382         //
383         // ACTIONS
384         //
385
386         /**
387          * Starts the web interface and registers all toadlets.
388          */
389         public void start() {
390                 registerToadlets();
391         }
392
393         /**
394          * Stops the web interface and unregisters all toadlets.
395          */
396         public void stop() {
397                 pageToadletRegistry.unregisterToadlets();
398         }
399
400         //
401         // PRIVATE METHODS
402         //
403
404         /**
405          * Register all toadlets.
406          */
407         private void registerToadlets() {
408                 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
409                 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
410                 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
411
412                 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
413                 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
414                 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
415                 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
416                 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
417                 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
418                 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
419                 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
420                 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
421                 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
422                 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
423                 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
424                 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
425                 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
426                 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
427                 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
428                 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
429                 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
430                 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
431                 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
432                 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
433                 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
434                 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
435                 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
436                 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
437                 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
438                 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
439                 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
440                 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
441                 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
442                 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
443                 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
444                 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
445                 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
446                 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
447                 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
448                 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
449                 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
450                 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
451                 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
452                 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
453                 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
454                 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
455                 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
456                 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
457                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
458                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
459                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
460                 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
461                 pageToadletRegistry.addPage(new GetImagePage(this));
462                 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
463                 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
464                 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
465                 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
466                 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
467                 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
468                 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
469                 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
470                 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
471                 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
472                 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
473                 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
474                 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
475                 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
476                 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
477                 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
478                 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
479                 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
480                 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
481                 pageToadletRegistry.addPage(new LikeAjaxPage(this));
482                 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
483                 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
484                 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
485                 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
486                 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
487                 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
488                 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
489
490                 pageToadletRegistry.registerToadlets();
491         }
492
493         /**
494          * Returns the Sone insert notification for the given Sone. If no
495          * notification for the given Sone exists, a new notification is created and
496          * cached.
497          *
498          * @param sone
499          *            The Sone to get the insert notification for
500          * @return The Sone insert notification
501          */
502         private TemplateNotification getSoneInsertNotification(Sone sone) {
503                 synchronized (soneInsertNotifications) {
504                         TemplateNotification templateNotification = soneInsertNotifications.get(sone);
505                         if (templateNotification == null) {
506                                 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
507                                 templateNotification.set("insertSone", sone);
508                                 soneInsertNotifications.put(sone, templateNotification);
509                         }
510                         return templateNotification;
511                 }
512         }
513
514         //
515         // EVENT HANDLERS
516         //
517
518         /**
519          * Notifies the web interface that a new {@link PostReply} was found.
520          *
521          * @param newPostReplyFoundEvent
522          *            The event
523          */
524         @Subscribe
525         public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
526                 PostReply reply = newPostReplyFoundEvent.getPostReply();
527                 boolean isLocal = reply.getSone().isLocal();
528                 if (isLocal) {
529                         localReplyNotification.add(reply);
530                 } else {
531                         newReplyNotification.add(reply);
532                 }
533                 if (!hasFirstStartNotification()) {
534                         notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
535                 }
536         }
537
538         @Subscribe
539         public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
540                 removePost(markPostKnownEvent.getPost());
541         }
542
543         @Subscribe
544         public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
545                 removeReply(markPostReplyKnownEvent.getPostReply());
546         }
547
548         @Subscribe
549         public void postRemoved(PostRemovedEvent postRemovedEvent) {
550                 removePost(postRemovedEvent.getPost());
551         }
552
553         private void removePost(Post post) {
554                 newPostNotification.remove(post);
555         }
556
557         @Subscribe
558         public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
559                 removeReply(postReplyRemovedEvent.getPostReply());
560         }
561
562         private void removeReply(PostReply reply) {
563                 newReplyNotification.remove(reply);
564                 localReplyNotification.remove(reply);
565         }
566
567         /**
568          * Notifies the web interface that a {@link Sone} is being inserted.
569          *
570          * @param soneInsertingEvent
571          *            The event
572          */
573         @Subscribe
574         public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
575                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone());
576                 soneInsertNotification.set("soneStatus", "inserting");
577                 if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
578                         notificationManager.addNotification(soneInsertNotification);
579                 }
580         }
581
582         /**
583          * Notifies the web interface that a {@link Sone} was inserted.
584          *
585          * @param soneInsertedEvent
586          *            The event
587          */
588         @Subscribe
589         public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
590                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone());
591                 soneInsertNotification.set("soneStatus", "inserted");
592                 soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000);
593                 if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
594                         notificationManager.addNotification(soneInsertNotification);
595                 }
596         }
597
598         /**
599          * Notifies the web interface that a {@link Sone} insert was aborted.
600          *
601          * @param soneInsertAbortedEvent
602          *            The event
603          */
604         @Subscribe
605         public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
606                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone());
607                 soneInsertNotification.set("soneStatus", "insert-aborted");
608                 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause());
609                 if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) {
610                         notificationManager.addNotification(soneInsertNotification);
611                 }
612         }
613
614         @Subscribe
615         public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
616                 pageToadletRegistry.activateDebugMode();
617         }
618
619 }