2 * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.web;
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
22 import static net.pterodactylus.util.template.TemplateParser.parse;
24 import java.io.StringReader;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
31 import java.util.TimeZone;
32 import java.util.UUID;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.ScheduledFuture;
36 import java.util.concurrent.TimeUnit;
37 import java.util.logging.Logger;
38 import javax.annotation.Nonnull;
39 import javax.annotation.Nullable;
41 import net.pterodactylus.sone.core.Core;
42 import net.pterodactylus.sone.core.ElementLoader;
43 import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
44 import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
45 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
46 import net.pterodactylus.sone.core.event.ImageInsertStartedEvent;
47 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
48 import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
49 import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
50 import net.pterodactylus.sone.core.event.NewPostFoundEvent;
51 import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
52 import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
53 import net.pterodactylus.sone.core.event.PostRemovedEvent;
54 import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
55 import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
56 import net.pterodactylus.sone.core.event.SoneInsertedEvent;
57 import net.pterodactylus.sone.core.event.SoneInsertingEvent;
58 import net.pterodactylus.sone.core.event.SoneLockedEvent;
59 import net.pterodactylus.sone.core.event.SoneRemovedEvent;
60 import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
61 import net.pterodactylus.sone.core.event.UpdateFoundEvent;
62 import net.pterodactylus.sone.data.Image;
63 import net.pterodactylus.sone.data.Post;
64 import net.pterodactylus.sone.data.PostReply;
65 import net.pterodactylus.sone.data.Sone;
66 import net.pterodactylus.sone.freenet.L10nFilter;
67 import net.pterodactylus.sone.main.Loaders;
68 import net.pterodactylus.sone.main.PluginHomepage;
69 import net.pterodactylus.sone.main.PluginVersion;
70 import net.pterodactylus.sone.main.PluginYear;
71 import net.pterodactylus.sone.main.SonePlugin;
72 import net.pterodactylus.sone.notify.ListNotification;
73 import net.pterodactylus.sone.notify.ListNotificationFilter;
74 import net.pterodactylus.sone.notify.PostVisibilityFilter;
75 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
76 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
77 import net.pterodactylus.sone.template.ParserFilter;
78 import net.pterodactylus.sone.template.RenderFilter;
79 import net.pterodactylus.sone.template.ShortenFilter;
80 import net.pterodactylus.sone.text.Part;
81 import net.pterodactylus.sone.text.SonePart;
82 import net.pterodactylus.sone.text.SoneTextParser;
83 import net.pterodactylus.sone.text.TimeTextConverter;
84 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
85 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
86 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
87 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
88 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
89 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
90 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
91 import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
92 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
93 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
94 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
95 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
96 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
97 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
98 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
99 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
100 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
101 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
102 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
103 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
104 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
105 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
106 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
107 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
108 import net.pterodactylus.sone.web.ajax.TrustAjaxPage;
109 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
110 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
111 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
112 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
113 import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
114 import net.pterodactylus.sone.web.page.FreenetRequest;
115 import net.pterodactylus.sone.web.page.TemplateRenderer;
116 import net.pterodactylus.sone.web.pages.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;
171 import freenet.clients.http.SessionManager;
172 import freenet.clients.http.SessionManager.Session;
173 import freenet.clients.http.ToadletContext;
174 import freenet.l10n.BaseL10n;
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;
183 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
184 * references to l10n helpers.
186 public class WebInterface implements SessionProvider {
189 private static final Logger logger = getLogger(WebInterface.class.getName());
191 /** The loaders for templates, pages, and classpath providers. */
192 private final Loaders loaders;
194 /** The notification manager. */
195 private final NotificationManager notificationManager = new NotificationManager();
197 /** The Sone plugin. */
198 private final SonePlugin sonePlugin;
200 /** The form password. */
201 private final String formPassword;
203 /** The template context factory. */
204 private final TemplateContextFactory templateContextFactory;
205 private final TemplateRenderer templateRenderer;
207 /** The Sone text parser. */
208 private final SoneTextParser soneTextParser;
210 /** The parser filter. */
211 private final ParserFilter parserFilter;
212 private final ShortenFilter shortenFilter;
213 private final RenderFilter renderFilter;
215 private final ListNotificationFilter listNotificationFilter;
216 private final PostVisibilityFilter postVisibilityFilter;
217 private final ReplyVisibilityFilter replyVisibilityFilter;
219 private final ElementLoader elementLoader;
220 private final LinkedElementRenderFilter linkedElementRenderFilter;
221 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
222 private final L10nFilter l10nFilter;
224 private final PageToadletRegistry pageToadletRegistry;
226 /** The “new Sone” notification. */
227 private final ListNotification<Sone> newSoneNotification;
229 /** The “new post” notification. */
230 private final ListNotification<Post> newPostNotification;
232 /** The “new reply” notification. */
233 private final ListNotification<PostReply> newReplyNotification;
235 /** The invisible “local post” notification. */
236 private final ListNotification<Post> localPostNotification;
238 /** The invisible “local reply” notification. */
239 private final ListNotification<PostReply> localReplyNotification;
241 /** The “you have been mentioned” notification. */
242 private final ListNotification<Post> mentionNotification;
244 /** Notifications for sone inserts. */
245 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
247 /** Sone locked notification ticker objects. */
248 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
250 /** The “Sone locked” notification. */
251 private final ListNotification<Sone> lockedSonesNotification;
253 /** The “new version” notification. */
254 private final TemplateNotification newVersionNotification;
256 /** The “inserting images” notification. */
257 private final ListNotification<Image> insertingImagesNotification;
259 /** The “inserted images” notification. */
260 private final ListNotification<Image> insertedImagesNotification;
262 /** The “image insert failed” notification. */
263 private final ListNotification<Image> imageInsertFailedNotification;
265 /** Scheduled executor for time-based notifications. */
266 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
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());
293 this.templateContextFactory = templateContextFactory;
294 templateContextFactory.addTemplateObject("webInterface", this);
295 templateContextFactory.addTemplateObject("formPassword", formPassword);
297 /* create notifications. */
298 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
299 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
301 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
302 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
304 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
305 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
307 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
308 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
310 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
311 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
313 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
314 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
316 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
317 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
319 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
320 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
322 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
323 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
325 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
326 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
328 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
329 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
337 * Returns the Sone core used by the Sone plugin.
339 * @return The Sone core
342 public Core getCore() {
343 return sonePlugin.core();
347 * Returns the template context factory of the web interface.
349 * @return The template context factory
351 public TemplateContextFactory getTemplateContextFactory() {
352 return templateContextFactory;
355 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
356 return getSessionManager().useSession(toadletContenxt);
359 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
360 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
361 if (session == null) {
362 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
367 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
368 Collection<Sone> localSones = getCore().getLocalSones();
369 if (localSones.size() == 1) {
370 return localSones.iterator().next();
372 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
375 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
376 Collection<Sone> localSones = getCore().getLocalSones();
377 if (localSones.size() == 1) {
378 return localSones.iterator().next();
380 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
384 * Returns the currently logged in Sone.
388 * @return The currently logged in Sone, or {@code null} if no Sone is
389 * currently logged in
391 private Sone getCurrentSone(Session session) {
392 if (session == null) {
395 String soneId = (String) session.getAttribute("Sone.CurrentSone");
396 if (soneId == null) {
399 return getCore().getLocalSone(soneId);
404 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
405 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
409 * Sets the currently logged in Sone.
411 * @param toadletContext
412 * The toadlet context
414 * The Sone to set as currently logged in
417 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
418 Session session = getOrCreateCurrentSession(toadletContext);
420 session.removeAttribute("Sone.CurrentSone");
422 session.setAttribute("Sone.CurrentSone", sone.getId());
427 * Returns the notification manager.
429 * @return The notification manager
431 public NotificationManager getNotifications() {
432 return notificationManager;
436 public Optional<Notification> getNotification(@Nonnull String notificationId) {
437 return Optional.fromNullable(notificationManager.getNotification(notificationId));
441 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
442 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
446 * Returns the l10n helper of the node.
448 * @return The node’s l10n helper
450 public BaseL10n getL10n() {
451 return sonePlugin.l10n().getBase();
455 * Returns the session manager of the node.
457 * @return The node’s session manager
459 public SessionManager getSessionManager() {
460 return sonePlugin.pluginRespirator().getSessionManager("Sone");
464 * Returns the node’s form password.
466 * @return The form password
468 public String getFormPassword() {
473 * Returns the posts that have been announced as new in the
474 * {@link #newPostNotification}.
476 * @return The new posts
478 public Set<Post> getNewPosts() {
479 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
483 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
484 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
485 .addAll(newPostNotification.getElements())
486 .addAll(localPostNotification.getElements())
488 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
492 * Returns the replies that have been announced as new in the
493 * {@link #newReplyNotification}.
495 * @return The new replies
497 public Set<PostReply> getNewReplies() {
498 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
502 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
503 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
504 .addAll(newReplyNotification.getElements())
505 .addAll(localReplyNotification.getElements())
507 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
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.
515 * {@code true} if no configuration file existed when Sone was
516 * loaded, {@code false} otherwise
518 public void setFirstStart(boolean firstStart) {
520 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
521 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
522 notificationManager.addNotification(firstStartNotification);
527 * Sets whether Sone was started with a fresh configuration file.
530 * {@code true} if Sone was started with a fresh configuration,
531 * {@code false} if the existing configuration could be read
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);
546 * Returns whether the first start notification is currently displayed.
548 * @return {@code true} if the first-start notification is currently
549 * displayed, {@code false} otherwise
551 private boolean hasFirstStartNotification() {
552 return notificationManager.getNotification("first-start-notification") != null;
560 * Starts the web interface and registers all toadlets.
562 public void start() {
565 /* notification templates. */
566 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
568 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
569 notificationManager.addNotification(startupNotification);
571 ticker.schedule(new Runnable() {
575 startupNotification.dismiss();
577 }, 2, TimeUnit.MINUTES);
579 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
580 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
581 ticker.scheduleAtFixedRate(new Runnable() {
584 @SuppressWarnings("synthetic-access")
586 if (getCore().getIdentityManager().isConnected()) {
587 wotMissingNotification.dismiss();
589 notificationManager.addNotification(wotMissingNotification);
593 }, 15, 15, TimeUnit.SECONDS);
597 * Stops the web interface and unregisters all toadlets.
600 pageToadletRegistry.unregisterToadlets();
601 ticker.shutdownNow();
609 * Register all toadlets.
611 private void registerToadlets() {
612 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
613 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
614 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
616 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
617 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
618 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
619 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
620 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
621 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
622 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
623 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
624 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
625 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
626 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
627 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
628 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
629 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
630 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
631 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
632 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
633 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
634 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
635 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
636 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
637 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
638 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
639 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
640 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
641 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
642 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
643 pageToadletRegistry.addPage(new TrustPage(this, loaders, templateRenderer));
644 pageToadletRegistry.addPage(new DistrustPage(this, loaders, templateRenderer));
645 pageToadletRegistry.addPage(new UntrustPage(this, loaders, templateRenderer));
646 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
647 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
648 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
649 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
650 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
651 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
652 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
653 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
654 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
655 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
656 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
657 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
658 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
659 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
660 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
661 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
662 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
663 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
664 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
665 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
666 pageToadletRegistry.addPage(new GetImagePage(this));
667 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
668 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
669 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
670 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
671 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
672 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
673 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
674 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
675 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
676 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
677 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
678 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
679 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
680 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
681 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
682 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
683 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
684 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
685 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
686 pageToadletRegistry.addPage(new TrustAjaxPage(this));
687 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
688 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
689 pageToadletRegistry.addPage(new LikeAjaxPage(this));
690 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
691 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
692 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
693 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
694 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
695 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
696 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
698 pageToadletRegistry.registerToadlets();
702 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
703 * {@link SonePart}s in the given text (after parsing it using
704 * {@link SoneTextParser}).
708 * @return All mentioned local Sones
710 private Collection<Sone> getMentionedSones(String text) {
711 /* we need no context to find mentioned Sones. */
712 Set<Sone> mentionedSones = new HashSet<>();
713 for (Part part : soneTextParser.parse(text, null)) {
714 if (part instanceof SonePart) {
715 mentionedSones.add(((SonePart) part).getSone());
718 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
722 * Returns the Sone insert notification for the given Sone. If no
723 * notification for the given Sone exists, a new notification is created and
727 * The Sone to get the insert notification for
728 * @return The Sone insert notification
730 private TemplateNotification getSoneInsertNotification(Sone sone) {
731 synchronized (soneInsertNotifications) {
732 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
733 if (templateNotification == null) {
734 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
735 templateNotification.set("insertSone", sone);
736 soneInsertNotifications.put(sone, templateNotification);
738 return templateNotification;
742 private boolean localSoneMentionedInNewPostOrReply(Post post) {
743 if (!post.getSone().isLocal()) {
744 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
748 for (PostReply postReply : getCore().getReplies(post.getId())) {
749 if (postReply.getSone().isLocal()) {
752 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
764 * Notifies the web interface that a new {@link Sone} was found.
766 * @param newSoneFoundEvent
770 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
771 newSoneNotification.add(newSoneFoundEvent.sone());
772 if (!hasFirstStartNotification()) {
773 notificationManager.addNotification(newSoneNotification);
778 * Notifies the web interface that a new {@link Post} was found.
780 * @param newPostFoundEvent
784 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
785 Post post = newPostFoundEvent.post();
786 boolean isLocal = post.getSone().isLocal();
788 localPostNotification.add(post);
790 newPostNotification.add(post);
792 if (!hasFirstStartNotification()) {
793 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
794 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
795 mentionNotification.add(post);
796 notificationManager.addNotification(mentionNotification);
799 getCore().markPostKnown(post);
804 * Notifies the web interface that a new {@link PostReply} was found.
806 * @param newPostReplyFoundEvent
810 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
811 PostReply reply = newPostReplyFoundEvent.postReply();
812 boolean isLocal = reply.getSone().isLocal();
814 localReplyNotification.add(reply);
816 newReplyNotification.add(reply);
818 if (!hasFirstStartNotification()) {
819 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
820 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
821 mentionNotification.add(reply.getPost().get());
822 notificationManager.addNotification(mentionNotification);
825 getCore().markReplyKnown(reply);
830 * Notifies the web interface that a {@link Sone} was marked as known.
832 * @param markSoneKnownEvent
836 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
837 newSoneNotification.remove(markSoneKnownEvent.sone());
841 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
842 removePost(markPostKnownEvent.post());
846 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
847 removeReply(markPostReplyKnownEvent.postReply());
851 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
852 newSoneNotification.remove(soneRemovedEvent.sone());
856 public void postRemoved(PostRemovedEvent postRemovedEvent) {
857 removePost(postRemovedEvent.post());
860 private void removePost(Post post) {
861 newPostNotification.remove(post);
862 localPostNotification.remove(post);
863 if (!localSoneMentionedInNewPostOrReply(post)) {
864 mentionNotification.remove(post);
869 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
870 removeReply(postReplyRemovedEvent.postReply());
873 private void removeReply(PostReply reply) {
874 newReplyNotification.remove(reply);
875 localReplyNotification.remove(reply);
876 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
877 mentionNotification.remove(reply.getPost().get());
882 * Notifies the web interface that a Sone was locked.
884 * @param soneLockedEvent
888 public void soneLocked(SoneLockedEvent soneLockedEvent) {
889 final Sone sone = soneLockedEvent.sone();
890 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
893 @SuppressWarnings("synthetic-access")
895 lockedSonesNotification.add(sone);
896 notificationManager.addNotification(lockedSonesNotification);
898 }, 5, TimeUnit.MINUTES);
899 lockedSonesTickerObjects.put(sone, tickerObject);
903 * Notifies the web interface that a Sone was unlocked.
905 * @param soneUnlockedEvent
909 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
910 lockedSonesNotification.remove(soneUnlockedEvent.sone());
911 lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
915 * Notifies the web interface that a {@link Sone} is being inserted.
917 * @param soneInsertingEvent
921 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
922 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.sone());
923 soneInsertNotification.set("soneStatus", "inserting");
924 if (soneInsertingEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
925 notificationManager.addNotification(soneInsertNotification);
930 * Notifies the web interface that a {@link Sone} was inserted.
932 * @param soneInsertedEvent
936 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
937 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.sone());
938 soneInsertNotification.set("soneStatus", "inserted");
939 soneInsertNotification.set("insertDuration", soneInsertedEvent.insertDuration() / 1000);
940 if (soneInsertedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
941 notificationManager.addNotification(soneInsertNotification);
946 * Notifies the web interface that a {@link Sone} insert was aborted.
948 * @param soneInsertAbortedEvent
952 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
953 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.sone());
954 soneInsertNotification.set("soneStatus", "insert-aborted");
955 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.cause());
956 if (soneInsertAbortedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
957 notificationManager.addNotification(soneInsertNotification);
962 * Notifies the web interface that a new Sone version was found.
964 * @param updateFoundEvent
968 public void updateFound(UpdateFoundEvent updateFoundEvent) {
969 newVersionNotification.set("latestVersion", updateFoundEvent.version());
970 newVersionNotification.set("latestEdition", updateFoundEvent.latestEdition());
971 newVersionNotification.set("releaseTime", updateFoundEvent.releaseTime());
972 newVersionNotification.set("disruptive", updateFoundEvent.disruptive());
973 notificationManager.addNotification(newVersionNotification);
977 * Notifies the web interface that an image insert was started
979 * @param imageInsertStartedEvent
983 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
984 insertingImagesNotification.add(imageInsertStartedEvent.image());
985 notificationManager.addNotification(insertingImagesNotification);
989 * Notifies the web interface that an {@link Image} insert was aborted.
991 * @param imageInsertAbortedEvent
995 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
996 insertingImagesNotification.remove(imageInsertAbortedEvent.image());
1000 * Notifies the web interface that an {@link Image} insert is finished.
1002 * @param imageInsertFinishedEvent
1006 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
1007 insertingImagesNotification.remove(imageInsertFinishedEvent.image());
1008 insertedImagesNotification.add(imageInsertFinishedEvent.image());
1009 notificationManager.addNotification(insertedImagesNotification);
1013 * Notifies the web interface that an {@link Image} insert has failed.
1015 * @param imageInsertFailedEvent
1019 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
1020 insertingImagesNotification.remove(imageInsertFailedEvent.image());
1021 imageInsertFailedNotification.add(imageInsertFailedEvent.image());
1022 notificationManager.addNotification(imageInsertFailedNotification);