♻️ Move generic error pages to custom classes
[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 import static net.pterodactylus.util.template.TemplateParser.parse;
23
24 import java.io.StringReader;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.TimeZone;
32 import java.util.UUID;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.ScheduledFuture;
36 import java.util.concurrent.TimeUnit;
37 import java.util.logging.Logger;
38 import javax.annotation.Nonnull;
39 import javax.annotation.Nullable;
40
41 import net.pterodactylus.sone.core.Core;
42 import net.pterodactylus.sone.core.ElementLoader;
43 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
44 import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
45 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
46 import net.pterodactylus.sone.core.event.ImageInsertStartedEvent;
47 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
48 import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
49 import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
50 import net.pterodactylus.sone.core.event.NewPostFoundEvent;
51 import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
52 import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
53 import net.pterodactylus.sone.core.event.PostRemovedEvent;
54 import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
55 import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
56 import net.pterodactylus.sone.core.event.SoneInsertedEvent;
57 import net.pterodactylus.sone.core.event.SoneInsertingEvent;
58 import net.pterodactylus.sone.core.event.SoneLockedEvent;
59 import net.pterodactylus.sone.core.event.SoneRemovedEvent;
60 import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
61 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
62 import net.pterodactylus.sone.data.Image;
63 import net.pterodactylus.sone.data.Post;
64 import net.pterodactylus.sone.data.PostReply;
65 import net.pterodactylus.sone.data.Sone;
66 import net.pterodactylus.sone.freenet.L10nFilter;
67 import net.pterodactylus.sone.main.Loaders;
68 import net.pterodactylus.sone.main.PluginHomepage;
69 import net.pterodactylus.sone.main.PluginVersion;
70 import net.pterodactylus.sone.main.PluginYear;
71 import net.pterodactylus.sone.main.SonePlugin;
72 import net.pterodactylus.sone.notify.ListNotification;
73 import net.pterodactylus.sone.notify.ListNotificationFilter;
74 import net.pterodactylus.sone.notify.PostVisibilityFilter;
75 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
76 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
77 import net.pterodactylus.sone.template.ParserFilter;
78 import net.pterodactylus.sone.template.RenderFilter;
79 import net.pterodactylus.sone.template.ShortenFilter;
80 import net.pterodactylus.sone.text.Part;
81 import net.pterodactylus.sone.text.SonePart;
82 import net.pterodactylus.sone.text.SoneTextParser;
83 import net.pterodactylus.sone.text.TimeTextConverter;
84 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
85 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
86 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
87 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
88 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
89 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
90 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
91 import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
92 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
93 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
94 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
95 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
96 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
97 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
98 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
99 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
100 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
101 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
102 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
103 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
104 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
105 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
106 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
107 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
108 import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
109 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
110 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
111 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
112 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
113 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
114 import net.pterodactylus.sone.web.page.FreenetRequest;
115 import net.pterodactylus.sone.web.page.TemplateRenderer;
116 import net.pterodactylus.sone.web.pages.AboutPage;
117 import net.pterodactylus.sone.web.pages.BookmarkPage;
118 import net.pterodactylus.sone.web.pages.BookmarksPage;
119 import net.pterodactylus.sone.web.pages.CreateAlbumPage;
120 import net.pterodactylus.sone.web.pages.CreatePostPage;
121 import net.pterodactylus.sone.web.pages.CreateReplyPage;
122 import net.pterodactylus.sone.web.pages.CreateSonePage;
123 import net.pterodactylus.sone.web.pages.DeleteAlbumPage;
124 import net.pterodactylus.sone.web.pages.DeleteImagePage;
125 import net.pterodactylus.sone.web.pages.DeletePostPage;
126 import net.pterodactylus.sone.web.pages.DeleteProfileFieldPage;
127 import net.pterodactylus.sone.web.pages.DeleteReplyPage;
128 import net.pterodactylus.sone.web.pages.DeleteSonePage;
129 import net.pterodactylus.sone.web.pages.DismissNotificationPage;
130 import net.pterodactylus.sone.web.pages.DistrustPage;
131 import net.pterodactylus.sone.web.pages.EditAlbumPage;
132 import net.pterodactylus.sone.web.pages.EditImagePage;
133 import net.pterodactylus.sone.web.pages.EditProfileFieldPage;
134 import net.pterodactylus.sone.web.pages.EditProfilePage;
135 import net.pterodactylus.sone.web.pages.EmptyAlbumTitlePage;
136 import net.pterodactylus.sone.web.pages.EmptyImageTitlePage;
137 import net.pterodactylus.sone.web.pages.FollowSonePage;
138 import net.pterodactylus.sone.web.pages.GetImagePage;
139 import net.pterodactylus.sone.web.pages.ImageBrowserPage;
140 import net.pterodactylus.sone.web.pages.IndexPage;
141 import net.pterodactylus.sone.web.pages.InvalidPage;
142 import net.pterodactylus.sone.web.pages.KnownSonesPage;
143 import net.pterodactylus.sone.web.pages.LikePage;
144 import net.pterodactylus.sone.web.pages.LockSonePage;
145 import net.pterodactylus.sone.web.pages.LoginPage;
146 import net.pterodactylus.sone.web.pages.LogoutPage;
147 import net.pterodactylus.sone.web.pages.MarkAsKnownPage;
148 import net.pterodactylus.sone.web.pages.NewPage;
149 import net.pterodactylus.sone.web.pages.NoPermissionPage;
150 import net.pterodactylus.sone.web.pages.OptionsPage;
151 import net.pterodactylus.sone.web.pages.RescuePage;
152 import net.pterodactylus.sone.web.pages.SearchPage;
153 import net.pterodactylus.sone.web.pages.SoneTemplatePage;
154 import net.pterodactylus.sone.web.pages.TrustPage;
155 import net.pterodactylus.sone.web.pages.UnbookmarkPage;
156 import net.pterodactylus.sone.web.pages.UnfollowSonePage;
157 import net.pterodactylus.sone.web.pages.UnlikePage;
158 import net.pterodactylus.sone.web.pages.UnlockSonePage;
159 import net.pterodactylus.sone.web.pages.UntrustPage;
160 import net.pterodactylus.sone.web.pages.UploadImagePage;
161 import net.pterodactylus.sone.web.pages.ViewPostPage;
162 import net.pterodactylus.sone.web.pages.ViewSonePage;
163 import net.pterodactylus.util.notify.Notification;
164 import net.pterodactylus.util.notify.NotificationManager;
165 import net.pterodactylus.util.notify.TemplateNotification;
166 import net.pterodactylus.util.template.Template;
167 import net.pterodactylus.util.template.TemplateContextFactory;
168 import net.pterodactylus.util.web.RedirectPage;
169 import net.pterodactylus.util.web.TemplatePage;
170
171 import freenet.clients.http.SessionManager;
172 import freenet.clients.http.SessionManager.Session;
173 import freenet.clients.http.ToadletContext;
174 import freenet.l10n.BaseL10n;
175
176 import com.google.common.base.Optional;
177 import com.google.common.collect.Collections2;
178 import com.google.common.collect.ImmutableSet;
179 import com.google.common.eventbus.Subscribe;
180 import com.google.inject.Inject;
181
182 /**
183  * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
184  * references to l10n helpers.
185  */
186 public class WebInterface implements SessionProvider {
187
188         /** The logger. */
189         private static final Logger logger = getLogger(WebInterface.class.getName());
190
191         /** The loaders for templates, pages, and classpath providers. */
192         private final Loaders loaders;
193
194         /** The notification manager. */
195         private final NotificationManager notificationManager = new NotificationManager();
196
197         /** The Sone plugin. */
198         private final SonePlugin sonePlugin;
199
200         /** The form password. */
201         private final String formPassword;
202
203         /** The template context factory. */
204         private final TemplateContextFactory templateContextFactory;
205         private final TemplateRenderer templateRenderer;
206
207         /** The Sone text parser. */
208         private final SoneTextParser soneTextParser;
209
210         /** The parser filter. */
211         private final ParserFilter parserFilter;
212         private final ShortenFilter shortenFilter;
213         private final RenderFilter renderFilter;
214
215         private final ListNotificationFilter listNotificationFilter;
216         private final PostVisibilityFilter postVisibilityFilter;
217         private final ReplyVisibilityFilter replyVisibilityFilter;
218
219         private final ElementLoader elementLoader;
220         private final LinkedElementRenderFilter linkedElementRenderFilter;
221         private final TimeTextConverter timeTextConverter = new TimeTextConverter();
222         private final L10nFilter l10nFilter;
223
224         private final PageToadletRegistry pageToadletRegistry;
225
226         /** The “new Sone” notification. */
227         private final ListNotification<Sone> newSoneNotification;
228
229         /** The “new post” notification. */
230         private final ListNotification<Post> newPostNotification;
231
232         /** The “new reply” notification. */
233         private final ListNotification<PostReply> newReplyNotification;
234
235         /** The invisible “local post” notification. */
236         private final ListNotification<Post> localPostNotification;
237
238         /** The invisible “local reply” notification. */
239         private final ListNotification<PostReply> localReplyNotification;
240
241         /** The “you have been mentioned” notification. */
242         private final ListNotification<Post> mentionNotification;
243
244         /** Notifications for sone inserts. */
245         private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
246
247         /** Sone locked notification ticker objects. */
248         private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
249
250         /** The “Sone locked” notification. */
251         private final ListNotification<Sone> lockedSonesNotification;
252
253         /** The “new version” notification. */
254         private final TemplateNotification newVersionNotification;
255
256         /** The “inserting images” notification. */
257         private final ListNotification<Image> insertingImagesNotification;
258
259         /** The “inserted images” notification. */
260         private final ListNotification<Image> insertedImagesNotification;
261
262         /** The “image insert failed” notification. */
263         private final ListNotification<Image> imageInsertFailedNotification;
264
265         /** Scheduled executor for time-based notifications. */
266         private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
267
268         @Inject
269         public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
270                         PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
271                         ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
272                         TemplateRenderer templateRenderer,
273                         ParserFilter parserFilter, ShortenFilter shortenFilter,
274                         RenderFilter renderFilter,
275                         LinkedElementRenderFilter linkedElementRenderFilter,
276                         PageToadletRegistry pageToadletRegistry) {
277                 this.sonePlugin = sonePlugin;
278                 this.loaders = loaders;
279                 this.listNotificationFilter = listNotificationFilter;
280                 this.postVisibilityFilter = postVisibilityFilter;
281                 this.replyVisibilityFilter = replyVisibilityFilter;
282                 this.elementLoader = elementLoader;
283                 this.templateRenderer = templateRenderer;
284                 this.parserFilter = parserFilter;
285                 this.shortenFilter = shortenFilter;
286                 this.renderFilter = renderFilter;
287                 this.linkedElementRenderFilter = linkedElementRenderFilter;
288                 this.pageToadletRegistry = pageToadletRegistry;
289                 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
290                 soneTextParser = new SoneTextParser(getCore(), getCore());
291                 l10nFilter = new L10nFilter(getL10n());
292
293                 this.templateContextFactory = templateContextFactory;
294                 templateContextFactory.addTemplateObject("webInterface", this);
295                 templateContextFactory.addTemplateObject("formPassword", formPassword);
296
297                 /* create notifications. */
298                 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
299                 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
300
301                 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
302                 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
303
304                 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
305                 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
306
307                 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
308                 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
309
310                 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
311                 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
312
313                 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
314                 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
315
316                 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
317                 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
318
319                 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
320                 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
321
322                 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
323                 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
324
325                 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
326                 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
327
328                 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
329                 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
330         }
331
332         //
333         // ACCESSORS
334         //
335
336         /**
337          * Returns the Sone core used by the Sone plugin.
338          *
339          * @return The Sone core
340          */
341         @Nonnull
342         public Core getCore() {
343                 return sonePlugin.core();
344         }
345
346         /**
347          * Returns the template context factory of the web interface.
348          *
349          * @return The template context factory
350          */
351         public TemplateContextFactory getTemplateContextFactory() {
352                 return templateContextFactory;
353         }
354
355         private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
356                 return getSessionManager().useSession(toadletContenxt);
357         }
358
359         private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
360                 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
361                 if (session == null) {
362                         session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
363                 }
364                 return session;
365         }
366
367         public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
368                 Collection<Sone> localSones = getCore().getLocalSones();
369                 if (localSones.size() == 1) {
370                         return localSones.iterator().next();
371                 }
372                 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
373         }
374
375         public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
376                 Collection<Sone> localSones = getCore().getLocalSones();
377                 if (localSones.size() == 1) {
378                         return localSones.iterator().next();
379                 }
380                 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
381         }
382
383         /**
384          * Returns the currently logged in Sone.
385          *
386          * @param session
387          *            The session
388          * @return The currently logged in Sone, or {@code null} if no Sone is
389          *         currently logged in
390          */
391         private Sone getCurrentSone(Session session) {
392                 if (session == null) {
393                         return null;
394                 }
395                 String soneId = (String) session.getAttribute("Sone.CurrentSone");
396                 if (soneId == null) {
397                         return null;
398                 }
399                 return getCore().getLocalSone(soneId);
400         }
401
402         @Override
403         @Nullable
404         public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
405                 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
406         }
407
408         /**
409          * Sets the currently logged in Sone.
410          *
411          * @param toadletContext
412          *            The toadlet context
413          * @param sone
414          *            The Sone to set as currently logged in
415          */
416         @Override
417         public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
418                 Session session = getOrCreateCurrentSession(toadletContext);
419                 if (sone == null) {
420                         session.removeAttribute("Sone.CurrentSone");
421                 } else {
422                         session.setAttribute("Sone.CurrentSone", sone.getId());
423                 }
424         }
425
426         /**
427          * Returns the notification manager.
428          *
429          * @return The notification manager
430          */
431         public NotificationManager getNotifications() {
432                 return notificationManager;
433         }
434
435         @Nonnull
436         public Optional<Notification> getNotification(@Nonnull String notificationId) {
437                 return Optional.fromNullable(notificationManager.getNotification(notificationId));
438         }
439
440         @Nonnull
441         public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
442                 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
443         }
444
445         /**
446          * Returns the l10n helper of the node.
447          *
448          * @return The node’s l10n helper
449          */
450         public BaseL10n getL10n() {
451                 return sonePlugin.l10n().getBase();
452         }
453
454         /**
455          * Returns the session manager of the node.
456          *
457          * @return The node’s session manager
458          */
459         public SessionManager getSessionManager() {
460                 return sonePlugin.pluginRespirator().getSessionManager("Sone");
461         }
462
463         /**
464          * Returns the node’s form password.
465          *
466          * @return The form password
467          */
468         public String getFormPassword() {
469                 return formPassword;
470         }
471
472         /**
473          * Returns the posts that have been announced as new in the
474          * {@link #newPostNotification}.
475          *
476          * @return The new posts
477          */
478         public Set<Post> getNewPosts() {
479                 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
480         }
481
482         @Nonnull
483         public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
484                 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
485                                 .addAll(newPostNotification.getElements())
486                                 .addAll(localPostNotification.getElements())
487                                 .build();
488                 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
489         }
490
491         /**
492          * Returns the replies that have been announced as new in the
493          * {@link #newReplyNotification}.
494          *
495          * @return The new replies
496          */
497         public Set<PostReply> getNewReplies() {
498                 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
499         }
500
501         @Nonnull
502         public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
503                 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
504                                 .addAll(newReplyNotification.getElements())
505                                 .addAll(localReplyNotification.getElements())
506                                 .build();
507                 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
508         }
509
510         /**
511          * Sets whether the current start of the plugin is the first start. It is
512          * considered a first start if the configuration file does not exist.
513          *
514          * @param firstStart
515          *            {@code true} if no configuration file existed when Sone was
516          *            loaded, {@code false} otherwise
517          */
518         public void setFirstStart(boolean firstStart) {
519                 if (firstStart) {
520                         Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
521                         Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
522                         notificationManager.addNotification(firstStartNotification);
523                 }
524         }
525
526         /**
527          * Sets whether Sone was started with a fresh configuration file.
528          *
529          * @param newConfig
530          *            {@code true} if Sone was started with a fresh configuration,
531          *            {@code false} if the existing configuration could be read
532          */
533         public void setNewConfig(boolean newConfig) {
534                 if (newConfig && !hasFirstStartNotification()) {
535                         Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
536                         Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
537                         notificationManager.addNotification(configNotReadNotification);
538                 }
539         }
540
541         //
542         // PRIVATE ACCESSORS
543         //
544
545         /**
546          * Returns whether the first start notification is currently displayed.
547          *
548          * @return {@code true} if the first-start notification is currently
549          *         displayed, {@code false} otherwise
550          */
551         private boolean hasFirstStartNotification() {
552                 return notificationManager.getNotification("first-start-notification") != null;
553         }
554
555         //
556         // ACTIONS
557         //
558
559         /**
560          * Starts the web interface and registers all toadlets.
561          */
562         public void start() {
563                 registerToadlets();
564
565                 /* notification templates. */
566                 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
567
568                 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
569                 notificationManager.addNotification(startupNotification);
570
571                 ticker.schedule(new Runnable() {
572
573                         @Override
574                         public void run() {
575                                 startupNotification.dismiss();
576                         }
577                 }, 2, TimeUnit.MINUTES);
578
579                 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
580                 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
581                 ticker.scheduleAtFixedRate(new Runnable() {
582
583                         @Override
584                         @SuppressWarnings("synthetic-access")
585                         public void run() {
586                                 if (getCore().getIdentityManager().isConnected()) {
587                                         wotMissingNotification.dismiss();
588                                 } else {
589                                         notificationManager.addNotification(wotMissingNotification);
590                                 }
591                         }
592
593                 }, 15, 15, TimeUnit.SECONDS);
594         }
595
596         /**
597          * Stops the web interface and unregisters all toadlets.
598          */
599         public void stop() {
600                 pageToadletRegistry.unregisterToadlets();
601                 ticker.shutdownNow();
602         }
603
604         //
605         // PRIVATE METHODS
606         //
607
608         /**
609          * Register all toadlets.
610          */
611         private void registerToadlets() {
612                 Template emptyTemplate = parse(new StringReader(""));
613                 Template loginTemplate = loaders.loadTemplate("/templates/login.html");
614                 Template indexTemplate = loaders.loadTemplate("/templates/index.html");
615                 Template newTemplate = loaders.loadTemplate("/templates/new.html");
616                 Template knownSonesTemplate = loaders.loadTemplate("/templates/knownSones.html");
617                 Template createSoneTemplate = loaders.loadTemplate("/templates/createSone.html");
618                 Template createPostTemplate = loaders.loadTemplate("/templates/createPost.html");
619                 Template createReplyTemplate = loaders.loadTemplate("/templates/createReply.html");
620                 Template bookmarksTemplate = loaders.loadTemplate("/templates/bookmarks.html");
621                 Template searchTemplate = loaders.loadTemplate("/templates/search.html");
622                 Template editProfileTemplate = loaders.loadTemplate("/templates/editProfile.html");
623                 Template editProfileFieldTemplate = loaders.loadTemplate("/templates/editProfileField.html");
624                 Template deleteProfileFieldTemplate = loaders.loadTemplate("/templates/deleteProfileField.html");
625                 Template viewSoneTemplate = loaders.loadTemplate("/templates/viewSone.html");
626                 Template viewPostTemplate = loaders.loadTemplate("/templates/viewPost.html");
627                 Template deletePostTemplate = loaders.loadTemplate("/templates/deletePost.html");
628                 Template deleteReplyTemplate = loaders.loadTemplate("/templates/deleteReply.html");
629                 Template deleteSoneTemplate = loaders.loadTemplate("/templates/deleteSone.html");
630                 Template imageBrowserTemplate = loaders.loadTemplate("/templates/imageBrowser.html");
631                 Template createAlbumTemplate = loaders.loadTemplate("/templates/createAlbum.html");
632                 Template deleteAlbumTemplate = loaders.loadTemplate("/templates/deleteAlbum.html");
633                 Template deleteImageTemplate = loaders.loadTemplate("/templates/deleteImage.html");
634                 Template optionsTemplate = loaders.loadTemplate("/templates/options.html");
635                 Template rescueTemplate = loaders.loadTemplate("/templates/rescue.html");
636                 Template aboutTemplate = loaders.loadTemplate("/templates/about.html");
637                 Template invalidTemplate = loaders.loadTemplate("/templates/invalid.html");
638                 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
639                 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
640                 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
641
642                 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
643                 pageToadletRegistry.addPage(new IndexPage(indexTemplate, this, loaders, templateRenderer, postVisibilityFilter));
644                 pageToadletRegistry.addPage(new NewPage(newTemplate, this, loaders, templateRenderer));
645                 pageToadletRegistry.addPage(new CreateSonePage(createSoneTemplate, this, loaders, templateRenderer));
646                 pageToadletRegistry.addPage(new KnownSonesPage(knownSonesTemplate, this, loaders, templateRenderer));
647                 pageToadletRegistry.addPage(new EditProfilePage(editProfileTemplate, this, loaders, templateRenderer));
648                 pageToadletRegistry.addPage(new EditProfileFieldPage(editProfileFieldTemplate, this, loaders, templateRenderer));
649                 pageToadletRegistry.addPage(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this, loaders, templateRenderer));
650                 pageToadletRegistry.addPage(new CreatePostPage(createPostTemplate, this, loaders, templateRenderer));
651                 pageToadletRegistry.addPage(new CreateReplyPage(createReplyTemplate, this, loaders, templateRenderer));
652                 pageToadletRegistry.addPage(new ViewSonePage(viewSoneTemplate, this, loaders, templateRenderer));
653                 pageToadletRegistry.addPage(new ViewPostPage(viewPostTemplate, this, loaders, templateRenderer));
654                 pageToadletRegistry.addPage(new LikePage(emptyTemplate, this, loaders, templateRenderer));
655                 pageToadletRegistry.addPage(new UnlikePage(emptyTemplate, this, loaders, templateRenderer));
656                 pageToadletRegistry.addPage(new DeletePostPage(deletePostTemplate, this, loaders, templateRenderer));
657                 pageToadletRegistry.addPage(new DeleteReplyPage(deleteReplyTemplate, this, loaders, templateRenderer));
658                 pageToadletRegistry.addPage(new LockSonePage(emptyTemplate, this, loaders, templateRenderer));
659                 pageToadletRegistry.addPage(new UnlockSonePage(emptyTemplate, this, loaders, templateRenderer));
660                 pageToadletRegistry.addPage(new FollowSonePage(emptyTemplate, this, loaders, templateRenderer));
661                 pageToadletRegistry.addPage(new UnfollowSonePage(emptyTemplate, this, loaders, templateRenderer));
662                 pageToadletRegistry.addPage(new ImageBrowserPage(imageBrowserTemplate, this, loaders, templateRenderer));
663                 pageToadletRegistry.addPage(new CreateAlbumPage(createAlbumTemplate, this, loaders, templateRenderer));
664                 pageToadletRegistry.addPage(new EditAlbumPage(emptyTemplate, this, loaders, templateRenderer));
665                 pageToadletRegistry.addPage(new DeleteAlbumPage(deleteAlbumTemplate, this, loaders, templateRenderer));
666                 pageToadletRegistry.addPage(new UploadImagePage(invalidTemplate, this, loaders, templateRenderer));
667                 pageToadletRegistry.addPage(new EditImagePage(emptyTemplate, this, loaders, templateRenderer));
668                 pageToadletRegistry.addPage(new DeleteImagePage(deleteImageTemplate, this, loaders, templateRenderer));
669                 pageToadletRegistry.addPage(new TrustPage(emptyTemplate, this, loaders, templateRenderer));
670                 pageToadletRegistry.addPage(new DistrustPage(emptyTemplate, this, loaders, templateRenderer));
671                 pageToadletRegistry.addPage(new UntrustPage(emptyTemplate, this, loaders, templateRenderer));
672                 pageToadletRegistry.addPage(new MarkAsKnownPage(emptyTemplate, this, loaders, templateRenderer));
673                 pageToadletRegistry.addPage(new BookmarkPage(emptyTemplate, this, loaders, templateRenderer));
674                 pageToadletRegistry.addPage(new UnbookmarkPage(emptyTemplate, this, loaders, templateRenderer));
675                 pageToadletRegistry.addPage(new BookmarksPage(bookmarksTemplate, this, loaders, templateRenderer));
676                 pageToadletRegistry.addPage(new SearchPage(searchTemplate, this, loaders, templateRenderer));
677                 pageToadletRegistry.addPage(new DeleteSonePage(deleteSoneTemplate, this, loaders, templateRenderer));
678                 pageToadletRegistry.addPage(new LoginPage(loginTemplate, this, loaders, templateRenderer));
679                 pageToadletRegistry.addPage(new LogoutPage(emptyTemplate, this, loaders, templateRenderer));
680                 pageToadletRegistry.addPage(new OptionsPage(optionsTemplate, this, loaders, templateRenderer));
681                 pageToadletRegistry.addPage(new RescuePage(rescueTemplate, this, loaders, templateRenderer));
682                 pageToadletRegistry.addPage(new AboutPage(aboutTemplate, this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
683                 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
684                 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
685                 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
686                 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
687                 pageToadletRegistry.addPage(new DismissNotificationPage(emptyTemplate, this, loaders, templateRenderer));
688                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
689                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
690                 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
691                 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
692                 pageToadletRegistry.addPage(new GetImagePage(this));
693                 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
694                 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
695                 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
696                 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
697                 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
698                 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
699                 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
700                 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
701                 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
702                 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
703                 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
704                 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
705                 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
706                 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
707                 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
708                 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
709                 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
710                 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
711                 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
712                 pageToadletRegistry.addPage(new TrustAjaxPage(this));
713                 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
714                 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
715                 pageToadletRegistry.addPage(new LikeAjaxPage(this));
716                 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
717                 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
718                 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
719                 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
720                 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
721                 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
722                 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
723
724                 pageToadletRegistry.registerToadlets();
725         }
726
727         /**
728          * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
729          * {@link SonePart}s in the given text (after parsing it using
730          * {@link SoneTextParser}).
731          *
732          * @param text
733          *            The text to parse
734          * @return All mentioned local Sones
735          */
736         private Collection<Sone> getMentionedSones(String text) {
737                 /* we need no context to find mentioned Sones. */
738                 Set<Sone> mentionedSones = new HashSet<>();
739                 for (Part part : soneTextParser.parse(text, null)) {
740                         if (part instanceof SonePart) {
741                                 mentionedSones.add(((SonePart) part).getSone());
742                         }
743                 }
744                 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
745         }
746
747         /**
748          * Returns the Sone insert notification for the given Sone. If no
749          * notification for the given Sone exists, a new notification is created and
750          * cached.
751          *
752          * @param sone
753          *            The Sone to get the insert notification for
754          * @return The Sone insert notification
755          */
756         private TemplateNotification getSoneInsertNotification(Sone sone) {
757                 synchronized (soneInsertNotifications) {
758                         TemplateNotification templateNotification = soneInsertNotifications.get(sone);
759                         if (templateNotification == null) {
760                                 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
761                                 templateNotification.set("insertSone", sone);
762                                 soneInsertNotifications.put(sone, templateNotification);
763                         }
764                         return templateNotification;
765                 }
766         }
767
768         private boolean localSoneMentionedInNewPostOrReply(Post post) {
769                 if (!post.getSone().isLocal()) {
770                         if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
771                                 return true;
772                         }
773                 }
774                 for (PostReply postReply : getCore().getReplies(post.getId())) {
775                         if (postReply.getSone().isLocal()) {
776                                 continue;
777                         }
778                         if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
779                                 return true;
780                         }
781                 }
782                 return false;
783         }
784
785         //
786         // EVENT HANDLERS
787         //
788
789         /**
790          * Notifies the web interface that a new {@link Sone} was found.
791          *
792          * @param newSoneFoundEvent
793          *            The event
794          */
795         @Subscribe
796         public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
797                 newSoneNotification.add(newSoneFoundEvent.sone());
798                 if (!hasFirstStartNotification()) {
799                         notificationManager.addNotification(newSoneNotification);
800                 }
801         }
802
803         /**
804          * Notifies the web interface that a new {@link Post} was found.
805          *
806          * @param newPostFoundEvent
807          *            The event
808          */
809         @Subscribe
810         public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
811                 Post post = newPostFoundEvent.post();
812                 boolean isLocal = post.getSone().isLocal();
813                 if (isLocal) {
814                         localPostNotification.add(post);
815                 } else {
816                         newPostNotification.add(post);
817                 }
818                 if (!hasFirstStartNotification()) {
819                         notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
820                         if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
821                                 mentionNotification.add(post);
822                                 notificationManager.addNotification(mentionNotification);
823                         }
824                 } else {
825                         getCore().markPostKnown(post);
826                 }
827         }
828
829         /**
830          * Notifies the web interface that a new {@link PostReply} was found.
831          *
832          * @param newPostReplyFoundEvent
833          *            The event
834          */
835         @Subscribe
836         public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
837                 PostReply reply = newPostReplyFoundEvent.postReply();
838                 boolean isLocal = reply.getSone().isLocal();
839                 if (isLocal) {
840                         localReplyNotification.add(reply);
841                 } else {
842                         newReplyNotification.add(reply);
843                 }
844                 if (!hasFirstStartNotification()) {
845                         notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
846                         if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
847                                 mentionNotification.add(reply.getPost().get());
848                                 notificationManager.addNotification(mentionNotification);
849                         }
850                 } else {
851                         getCore().markReplyKnown(reply);
852                 }
853         }
854
855         /**
856          * Notifies the web interface that a {@link Sone} was marked as known.
857          *
858          * @param markSoneKnownEvent
859          *            The event
860          */
861         @Subscribe
862         public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
863                 newSoneNotification.remove(markSoneKnownEvent.sone());
864         }
865
866         @Subscribe
867         public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
868                 removePost(markPostKnownEvent.post());
869         }
870
871         @Subscribe
872         public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
873                 removeReply(markPostReplyKnownEvent.postReply());
874         }
875
876         @Subscribe
877         public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
878                 newSoneNotification.remove(soneRemovedEvent.sone());
879         }
880
881         @Subscribe
882         public void postRemoved(PostRemovedEvent postRemovedEvent) {
883                 removePost(postRemovedEvent.post());
884         }
885
886         private void removePost(Post post) {
887                 newPostNotification.remove(post);
888                 localPostNotification.remove(post);
889                 if (!localSoneMentionedInNewPostOrReply(post)) {
890                         mentionNotification.remove(post);
891                 }
892         }
893
894         @Subscribe
895         public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
896                 removeReply(postReplyRemovedEvent.postReply());
897         }
898
899         private void removeReply(PostReply reply) {
900                 newReplyNotification.remove(reply);
901                 localReplyNotification.remove(reply);
902                 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
903                         mentionNotification.remove(reply.getPost().get());
904                 }
905         }
906
907         /**
908          * Notifies the web interface that a Sone was locked.
909          *
910          * @param soneLockedEvent
911          *            The event
912          */
913         @Subscribe
914         public void soneLocked(SoneLockedEvent soneLockedEvent) {
915                 final Sone sone = soneLockedEvent.sone();
916                 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
917
918                         @Override
919                         @SuppressWarnings("synthetic-access")
920                         public void run() {
921                                 lockedSonesNotification.add(sone);
922                                 notificationManager.addNotification(lockedSonesNotification);
923                         }
924                 }, 5, TimeUnit.MINUTES);
925                 lockedSonesTickerObjects.put(sone, tickerObject);
926         }
927
928         /**
929          * Notifies the web interface that a Sone was unlocked.
930          *
931          * @param soneUnlockedEvent
932          *            The event
933          */
934         @Subscribe
935         public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
936                 lockedSonesNotification.remove(soneUnlockedEvent.sone());
937                 lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
938         }
939
940         /**
941          * Notifies the web interface that a {@link Sone} is being inserted.
942          *
943          * @param soneInsertingEvent
944          *            The event
945          */
946         @Subscribe
947         public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
948                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.sone());
949                 soneInsertNotification.set("soneStatus", "inserting");
950                 if (soneInsertingEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
951                         notificationManager.addNotification(soneInsertNotification);
952                 }
953         }
954
955         /**
956          * Notifies the web interface that a {@link Sone} was inserted.
957          *
958          * @param soneInsertedEvent
959          *            The event
960          */
961         @Subscribe
962         public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
963                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.sone());
964                 soneInsertNotification.set("soneStatus", "inserted");
965                 soneInsertNotification.set("insertDuration", soneInsertedEvent.insertDuration() / 1000);
966                 if (soneInsertedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
967                         notificationManager.addNotification(soneInsertNotification);
968                 }
969         }
970
971         /**
972          * Notifies the web interface that a {@link Sone} insert was aborted.
973          *
974          * @param soneInsertAbortedEvent
975          *            The event
976          */
977         @Subscribe
978         public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
979                 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.sone());
980                 soneInsertNotification.set("soneStatus", "insert-aborted");
981                 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.cause());
982                 if (soneInsertAbortedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
983                         notificationManager.addNotification(soneInsertNotification);
984                 }
985         }
986
987         /**
988          * Notifies the web interface that a new Sone version was found.
989          *
990          * @param updateFoundEvent
991          *            The event
992          */
993         @Subscribe
994         public void updateFound(UpdateFoundEvent updateFoundEvent) {
995                 newVersionNotification.set("latestVersion", updateFoundEvent.version());
996                 newVersionNotification.set("latestEdition", updateFoundEvent.latestEdition());
997                 newVersionNotification.set("releaseTime", updateFoundEvent.releaseTime());
998                 newVersionNotification.set("disruptive", updateFoundEvent.disruptive());
999                 notificationManager.addNotification(newVersionNotification);
1000         }
1001
1002         /**
1003          * Notifies the web interface that an image insert was started
1004          *
1005          * @param imageInsertStartedEvent
1006          *            The event
1007          */
1008         @Subscribe
1009         public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
1010                 insertingImagesNotification.add(imageInsertStartedEvent.image());
1011                 notificationManager.addNotification(insertingImagesNotification);
1012         }
1013
1014         /**
1015          * Notifies the web interface that an {@link Image} insert was aborted.
1016          *
1017          * @param imageInsertAbortedEvent
1018          *            The event
1019          */
1020         @Subscribe
1021         public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
1022                 insertingImagesNotification.remove(imageInsertAbortedEvent.image());
1023         }
1024
1025         /**
1026          * Notifies the web interface that an {@link Image} insert is finished.
1027          *
1028          * @param imageInsertFinishedEvent
1029          *            The event
1030          */
1031         @Subscribe
1032         public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
1033                 insertingImagesNotification.remove(imageInsertFinishedEvent.image());
1034                 insertedImagesNotification.add(imageInsertFinishedEvent.image());
1035                 notificationManager.addNotification(insertedImagesNotification);
1036         }
1037
1038         /**
1039          * Notifies the web interface that an {@link Image} insert has failed.
1040          *
1041          * @param imageInsertFailedEvent
1042          *            The event
1043          */
1044         @Subscribe
1045         public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
1046                 insertingImagesNotification.remove(imageInsertFailedEvent.image());
1047                 imageInsertFailedNotification.add(imageInsertFailedEvent.image());
1048                 notificationManager.addNotification(imageInsertFailedNotification);
1049         }
1050
1051 }