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 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");
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));
724 pageToadletRegistry.registerToadlets();
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}).
734 * @return All mentioned local Sones
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());
744 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
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
753 * The Sone to get the insert notification for
754 * @return The Sone insert notification
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);
764 return templateNotification;
768 private boolean localSoneMentionedInNewPostOrReply(Post post) {
769 if (!post.getSone().isLocal()) {
770 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
774 for (PostReply postReply : getCore().getReplies(post.getId())) {
775 if (postReply.getSone().isLocal()) {
778 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
790 * Notifies the web interface that a new {@link Sone} was found.
792 * @param newSoneFoundEvent
796 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
797 newSoneNotification.add(newSoneFoundEvent.sone());
798 if (!hasFirstStartNotification()) {
799 notificationManager.addNotification(newSoneNotification);
804 * Notifies the web interface that a new {@link Post} was found.
806 * @param newPostFoundEvent
810 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
811 Post post = newPostFoundEvent.post();
812 boolean isLocal = post.getSone().isLocal();
814 localPostNotification.add(post);
816 newPostNotification.add(post);
818 if (!hasFirstStartNotification()) {
819 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
820 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
821 mentionNotification.add(post);
822 notificationManager.addNotification(mentionNotification);
825 getCore().markPostKnown(post);
830 * Notifies the web interface that a new {@link PostReply} was found.
832 * @param newPostReplyFoundEvent
836 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
837 PostReply reply = newPostReplyFoundEvent.postReply();
838 boolean isLocal = reply.getSone().isLocal();
840 localReplyNotification.add(reply);
842 newReplyNotification.add(reply);
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);
851 getCore().markReplyKnown(reply);
856 * Notifies the web interface that a {@link Sone} was marked as known.
858 * @param markSoneKnownEvent
862 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
863 newSoneNotification.remove(markSoneKnownEvent.sone());
867 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
868 removePost(markPostKnownEvent.post());
872 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
873 removeReply(markPostReplyKnownEvent.postReply());
877 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
878 newSoneNotification.remove(soneRemovedEvent.sone());
882 public void postRemoved(PostRemovedEvent postRemovedEvent) {
883 removePost(postRemovedEvent.post());
886 private void removePost(Post post) {
887 newPostNotification.remove(post);
888 localPostNotification.remove(post);
889 if (!localSoneMentionedInNewPostOrReply(post)) {
890 mentionNotification.remove(post);
895 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
896 removeReply(postReplyRemovedEvent.postReply());
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());
908 * Notifies the web interface that a Sone was locked.
910 * @param soneLockedEvent
914 public void soneLocked(SoneLockedEvent soneLockedEvent) {
915 final Sone sone = soneLockedEvent.sone();
916 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
919 @SuppressWarnings("synthetic-access")
921 lockedSonesNotification.add(sone);
922 notificationManager.addNotification(lockedSonesNotification);
924 }, 5, TimeUnit.MINUTES);
925 lockedSonesTickerObjects.put(sone, tickerObject);
929 * Notifies the web interface that a Sone was unlocked.
931 * @param soneUnlockedEvent
935 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
936 lockedSonesNotification.remove(soneUnlockedEvent.sone());
937 lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
941 * Notifies the web interface that a {@link Sone} is being inserted.
943 * @param soneInsertingEvent
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);
956 * Notifies the web interface that a {@link Sone} was inserted.
958 * @param soneInsertedEvent
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);
972 * Notifies the web interface that a {@link Sone} insert was aborted.
974 * @param soneInsertAbortedEvent
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);
988 * Notifies the web interface that a new Sone version was found.
990 * @param updateFoundEvent
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);
1003 * Notifies the web interface that an image insert was started
1005 * @param imageInsertStartedEvent
1009 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
1010 insertingImagesNotification.add(imageInsertStartedEvent.image());
1011 notificationManager.addNotification(insertingImagesNotification);
1015 * Notifies the web interface that an {@link Image} insert was aborted.
1017 * @param imageInsertAbortedEvent
1021 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
1022 insertingImagesNotification.remove(imageInsertAbortedEvent.image());
1026 * Notifies the web interface that an {@link Image} insert is finished.
1028 * @param imageInsertFinishedEvent
1032 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
1033 insertingImagesNotification.remove(imageInsertFinishedEvent.image());
1034 insertedImagesNotification.add(imageInsertFinishedEvent.image());
1035 notificationManager.addNotification(insertedImagesNotification);
1039 * Notifies the web interface that an {@link Image} insert has failed.
1041 * @param imageInsertFailedEvent
1045 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
1046 insertingImagesNotification.remove(imageInsertFailedEvent.image());
1047 imageInsertFailedNotification.add(imageInsertFailedEvent.image());
1048 notificationManager.addNotification(imageInsertFailedNotification);