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.FollowSonePage;
136 import net.pterodactylus.sone.web.pages.GetImagePage;
137 import net.pterodactylus.sone.web.pages.ImageBrowserPage;
138 import net.pterodactylus.sone.web.pages.IndexPage;
139 import net.pterodactylus.sone.web.pages.KnownSonesPage;
140 import net.pterodactylus.sone.web.pages.LikePage;
141 import net.pterodactylus.sone.web.pages.LockSonePage;
142 import net.pterodactylus.sone.web.pages.LoginPage;
143 import net.pterodactylus.sone.web.pages.LogoutPage;
144 import net.pterodactylus.sone.web.pages.MarkAsKnownPage;
145 import net.pterodactylus.sone.web.pages.NewPage;
146 import net.pterodactylus.sone.web.pages.OptionsPage;
147 import net.pterodactylus.sone.web.pages.RescuePage;
148 import net.pterodactylus.sone.web.pages.SearchPage;
149 import net.pterodactylus.sone.web.pages.SoneTemplatePage;
150 import net.pterodactylus.sone.web.pages.TrustPage;
151 import net.pterodactylus.sone.web.pages.UnbookmarkPage;
152 import net.pterodactylus.sone.web.pages.UnfollowSonePage;
153 import net.pterodactylus.sone.web.pages.UnlikePage;
154 import net.pterodactylus.sone.web.pages.UnlockSonePage;
155 import net.pterodactylus.sone.web.pages.UntrustPage;
156 import net.pterodactylus.sone.web.pages.UploadImagePage;
157 import net.pterodactylus.sone.web.pages.ViewPostPage;
158 import net.pterodactylus.sone.web.pages.ViewSonePage;
159 import net.pterodactylus.util.notify.Notification;
160 import net.pterodactylus.util.notify.NotificationManager;
161 import net.pterodactylus.util.notify.TemplateNotification;
162 import net.pterodactylus.util.template.Template;
163 import net.pterodactylus.util.template.TemplateContextFactory;
164 import net.pterodactylus.util.web.RedirectPage;
165 import net.pterodactylus.util.web.TemplatePage;
167 import freenet.clients.http.SessionManager;
168 import freenet.clients.http.SessionManager.Session;
169 import freenet.clients.http.ToadletContext;
170 import freenet.l10n.BaseL10n;
172 import com.google.common.base.Optional;
173 import com.google.common.collect.Collections2;
174 import com.google.common.collect.ImmutableSet;
175 import com.google.common.eventbus.Subscribe;
176 import com.google.inject.Inject;
179 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
180 * references to l10n helpers.
182 public class WebInterface implements SessionProvider {
185 private static final Logger logger = getLogger(WebInterface.class.getName());
187 /** The loaders for templates, pages, and classpath providers. */
188 private final Loaders loaders;
190 /** The notification manager. */
191 private final NotificationManager notificationManager = new NotificationManager();
193 /** The Sone plugin. */
194 private final SonePlugin sonePlugin;
196 /** The form password. */
197 private final String formPassword;
199 /** The template context factory. */
200 private final TemplateContextFactory templateContextFactory;
201 private final TemplateRenderer templateRenderer;
203 /** The Sone text parser. */
204 private final SoneTextParser soneTextParser;
206 /** The parser filter. */
207 private final ParserFilter parserFilter;
208 private final ShortenFilter shortenFilter;
209 private final RenderFilter renderFilter;
211 private final ListNotificationFilter listNotificationFilter;
212 private final PostVisibilityFilter postVisibilityFilter;
213 private final ReplyVisibilityFilter replyVisibilityFilter;
215 private final ElementLoader elementLoader;
216 private final LinkedElementRenderFilter linkedElementRenderFilter;
217 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
218 private final L10nFilter l10nFilter;
220 private final PageToadletRegistry pageToadletRegistry;
222 /** The “new Sone” notification. */
223 private final ListNotification<Sone> newSoneNotification;
225 /** The “new post” notification. */
226 private final ListNotification<Post> newPostNotification;
228 /** The “new reply” notification. */
229 private final ListNotification<PostReply> newReplyNotification;
231 /** The invisible “local post” notification. */
232 private final ListNotification<Post> localPostNotification;
234 /** The invisible “local reply” notification. */
235 private final ListNotification<PostReply> localReplyNotification;
237 /** The “you have been mentioned” notification. */
238 private final ListNotification<Post> mentionNotification;
240 /** Notifications for sone inserts. */
241 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
243 /** Sone locked notification ticker objects. */
244 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
246 /** The “Sone locked” notification. */
247 private final ListNotification<Sone> lockedSonesNotification;
249 /** The “new version” notification. */
250 private final TemplateNotification newVersionNotification;
252 /** The “inserting images” notification. */
253 private final ListNotification<Image> insertingImagesNotification;
255 /** The “inserted images” notification. */
256 private final ListNotification<Image> insertedImagesNotification;
258 /** The “image insert failed” notification. */
259 private final ListNotification<Image> imageInsertFailedNotification;
261 /** Scheduled executor for time-based notifications. */
262 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
265 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
266 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
267 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
268 TemplateRenderer templateRenderer,
269 ParserFilter parserFilter, ShortenFilter shortenFilter,
270 RenderFilter renderFilter,
271 LinkedElementRenderFilter linkedElementRenderFilter,
272 PageToadletRegistry pageToadletRegistry) {
273 this.sonePlugin = sonePlugin;
274 this.loaders = loaders;
275 this.listNotificationFilter = listNotificationFilter;
276 this.postVisibilityFilter = postVisibilityFilter;
277 this.replyVisibilityFilter = replyVisibilityFilter;
278 this.elementLoader = elementLoader;
279 this.templateRenderer = templateRenderer;
280 this.parserFilter = parserFilter;
281 this.shortenFilter = shortenFilter;
282 this.renderFilter = renderFilter;
283 this.linkedElementRenderFilter = linkedElementRenderFilter;
284 this.pageToadletRegistry = pageToadletRegistry;
285 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
286 soneTextParser = new SoneTextParser(getCore(), getCore());
287 l10nFilter = new L10nFilter(getL10n());
289 this.templateContextFactory = templateContextFactory;
290 templateContextFactory.addTemplateObject("webInterface", this);
291 templateContextFactory.addTemplateObject("formPassword", formPassword);
293 /* create notifications. */
294 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
295 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
297 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
298 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
300 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
301 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
303 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
304 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
306 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
307 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
309 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
310 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
312 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
313 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
315 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
316 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
318 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
319 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
321 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
322 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
324 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
325 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
333 * Returns the Sone core used by the Sone plugin.
335 * @return The Sone core
338 public Core getCore() {
339 return sonePlugin.core();
343 * Returns the template context factory of the web interface.
345 * @return The template context factory
347 public TemplateContextFactory getTemplateContextFactory() {
348 return templateContextFactory;
351 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
352 return getSessionManager().useSession(toadletContenxt);
355 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
356 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
357 if (session == null) {
358 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
363 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
364 Collection<Sone> localSones = getCore().getLocalSones();
365 if (localSones.size() == 1) {
366 return localSones.iterator().next();
368 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
371 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
372 Collection<Sone> localSones = getCore().getLocalSones();
373 if (localSones.size() == 1) {
374 return localSones.iterator().next();
376 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
380 * Returns the currently logged in Sone.
384 * @return The currently logged in Sone, or {@code null} if no Sone is
385 * currently logged in
387 private Sone getCurrentSone(Session session) {
388 if (session == null) {
391 String soneId = (String) session.getAttribute("Sone.CurrentSone");
392 if (soneId == null) {
395 return getCore().getLocalSone(soneId);
400 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
401 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
405 * Sets the currently logged in Sone.
407 * @param toadletContext
408 * The toadlet context
410 * The Sone to set as currently logged in
413 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
414 Session session = getOrCreateCurrentSession(toadletContext);
416 session.removeAttribute("Sone.CurrentSone");
418 session.setAttribute("Sone.CurrentSone", sone.getId());
423 * Returns the notification manager.
425 * @return The notification manager
427 public NotificationManager getNotifications() {
428 return notificationManager;
432 public Optional<Notification> getNotification(@Nonnull String notificationId) {
433 return Optional.fromNullable(notificationManager.getNotification(notificationId));
437 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
438 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
442 * Returns the l10n helper of the node.
444 * @return The node’s l10n helper
446 public BaseL10n getL10n() {
447 return sonePlugin.l10n().getBase();
451 * Returns the session manager of the node.
453 * @return The node’s session manager
455 public SessionManager getSessionManager() {
456 return sonePlugin.pluginRespirator().getSessionManager("Sone");
460 * Returns the node’s form password.
462 * @return The form password
464 public String getFormPassword() {
469 * Returns the posts that have been announced as new in the
470 * {@link #newPostNotification}.
472 * @return The new posts
474 public Set<Post> getNewPosts() {
475 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
479 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
480 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
481 .addAll(newPostNotification.getElements())
482 .addAll(localPostNotification.getElements())
484 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
488 * Returns the replies that have been announced as new in the
489 * {@link #newReplyNotification}.
491 * @return The new replies
493 public Set<PostReply> getNewReplies() {
494 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
498 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
499 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
500 .addAll(newReplyNotification.getElements())
501 .addAll(localReplyNotification.getElements())
503 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
507 * Sets whether the current start of the plugin is the first start. It is
508 * considered a first start if the configuration file does not exist.
511 * {@code true} if no configuration file existed when Sone was
512 * loaded, {@code false} otherwise
514 public void setFirstStart(boolean firstStart) {
516 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
517 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
518 notificationManager.addNotification(firstStartNotification);
523 * Sets whether Sone was started with a fresh configuration file.
526 * {@code true} if Sone was started with a fresh configuration,
527 * {@code false} if the existing configuration could be read
529 public void setNewConfig(boolean newConfig) {
530 if (newConfig && !hasFirstStartNotification()) {
531 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
532 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
533 notificationManager.addNotification(configNotReadNotification);
542 * Returns whether the first start notification is currently displayed.
544 * @return {@code true} if the first-start notification is currently
545 * displayed, {@code false} otherwise
547 private boolean hasFirstStartNotification() {
548 return notificationManager.getNotification("first-start-notification") != null;
556 * Starts the web interface and registers all toadlets.
558 public void start() {
561 /* notification templates. */
562 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
564 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
565 notificationManager.addNotification(startupNotification);
567 ticker.schedule(new Runnable() {
571 startupNotification.dismiss();
573 }, 2, TimeUnit.MINUTES);
575 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
576 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
577 ticker.scheduleAtFixedRate(new Runnable() {
580 @SuppressWarnings("synthetic-access")
582 if (getCore().getIdentityManager().isConnected()) {
583 wotMissingNotification.dismiss();
585 notificationManager.addNotification(wotMissingNotification);
589 }, 15, 15, TimeUnit.SECONDS);
593 * Stops the web interface and unregisters all toadlets.
596 pageToadletRegistry.unregisterToadlets();
597 ticker.shutdownNow();
605 * Register all toadlets.
607 private void registerToadlets() {
608 Template emptyTemplate = parse(new StringReader(""));
609 Template loginTemplate = loaders.loadTemplate("/templates/login.html");
610 Template indexTemplate = loaders.loadTemplate("/templates/index.html");
611 Template newTemplate = loaders.loadTemplate("/templates/new.html");
612 Template knownSonesTemplate = loaders.loadTemplate("/templates/knownSones.html");
613 Template createSoneTemplate = loaders.loadTemplate("/templates/createSone.html");
614 Template createPostTemplate = loaders.loadTemplate("/templates/createPost.html");
615 Template createReplyTemplate = loaders.loadTemplate("/templates/createReply.html");
616 Template bookmarksTemplate = loaders.loadTemplate("/templates/bookmarks.html");
617 Template searchTemplate = loaders.loadTemplate("/templates/search.html");
618 Template editProfileTemplate = loaders.loadTemplate("/templates/editProfile.html");
619 Template editProfileFieldTemplate = loaders.loadTemplate("/templates/editProfileField.html");
620 Template deleteProfileFieldTemplate = loaders.loadTemplate("/templates/deleteProfileField.html");
621 Template viewSoneTemplate = loaders.loadTemplate("/templates/viewSone.html");
622 Template viewPostTemplate = loaders.loadTemplate("/templates/viewPost.html");
623 Template deletePostTemplate = loaders.loadTemplate("/templates/deletePost.html");
624 Template deleteReplyTemplate = loaders.loadTemplate("/templates/deleteReply.html");
625 Template deleteSoneTemplate = loaders.loadTemplate("/templates/deleteSone.html");
626 Template imageBrowserTemplate = loaders.loadTemplate("/templates/imageBrowser.html");
627 Template createAlbumTemplate = loaders.loadTemplate("/templates/createAlbum.html");
628 Template deleteAlbumTemplate = loaders.loadTemplate("/templates/deleteAlbum.html");
629 Template deleteImageTemplate = loaders.loadTemplate("/templates/deleteImage.html");
630 Template noPermissionTemplate = loaders.loadTemplate("/templates/noPermission.html");
631 Template emptyImageTitleTemplate = loaders.loadTemplate("/templates/emptyImageTitle.html");
632 Template emptyAlbumTitleTemplate = loaders.loadTemplate("/templates/emptyAlbumTitle.html");
633 Template optionsTemplate = loaders.loadTemplate("/templates/options.html");
634 Template rescueTemplate = loaders.loadTemplate("/templates/rescue.html");
635 Template aboutTemplate = loaders.loadTemplate("/templates/about.html");
636 Template invalidTemplate = loaders.loadTemplate("/templates/invalid.html");
637 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
638 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
639 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
641 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
642 pageToadletRegistry.addPage(new IndexPage(indexTemplate, this, loaders, templateRenderer, postVisibilityFilter));
643 pageToadletRegistry.addPage(new NewPage(newTemplate, this, loaders, templateRenderer));
644 pageToadletRegistry.addPage(new CreateSonePage(createSoneTemplate, this, loaders, templateRenderer));
645 pageToadletRegistry.addPage(new KnownSonesPage(knownSonesTemplate, this, loaders, templateRenderer));
646 pageToadletRegistry.addPage(new EditProfilePage(editProfileTemplate, this, loaders, templateRenderer));
647 pageToadletRegistry.addPage(new EditProfileFieldPage(editProfileFieldTemplate, this, loaders, templateRenderer));
648 pageToadletRegistry.addPage(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this, loaders, templateRenderer));
649 pageToadletRegistry.addPage(new CreatePostPage(createPostTemplate, this, loaders, templateRenderer));
650 pageToadletRegistry.addPage(new CreateReplyPage(createReplyTemplate, this, loaders, templateRenderer));
651 pageToadletRegistry.addPage(new ViewSonePage(viewSoneTemplate, this, loaders, templateRenderer));
652 pageToadletRegistry.addPage(new ViewPostPage(viewPostTemplate, this, loaders, templateRenderer));
653 pageToadletRegistry.addPage(new LikePage(emptyTemplate, this, loaders, templateRenderer));
654 pageToadletRegistry.addPage(new UnlikePage(emptyTemplate, this, loaders, templateRenderer));
655 pageToadletRegistry.addPage(new DeletePostPage(deletePostTemplate, this, loaders, templateRenderer));
656 pageToadletRegistry.addPage(new DeleteReplyPage(deleteReplyTemplate, this, loaders, templateRenderer));
657 pageToadletRegistry.addPage(new LockSonePage(emptyTemplate, this, loaders, templateRenderer));
658 pageToadletRegistry.addPage(new UnlockSonePage(emptyTemplate, this, loaders, templateRenderer));
659 pageToadletRegistry.addPage(new FollowSonePage(emptyTemplate, this, loaders, templateRenderer));
660 pageToadletRegistry.addPage(new UnfollowSonePage(emptyTemplate, this, loaders, templateRenderer));
661 pageToadletRegistry.addPage(new ImageBrowserPage(imageBrowserTemplate, this, loaders, templateRenderer));
662 pageToadletRegistry.addPage(new CreateAlbumPage(createAlbumTemplate, this, loaders, templateRenderer));
663 pageToadletRegistry.addPage(new EditAlbumPage(emptyTemplate, this, loaders, templateRenderer));
664 pageToadletRegistry.addPage(new DeleteAlbumPage(deleteAlbumTemplate, this, loaders, templateRenderer));
665 pageToadletRegistry.addPage(new UploadImagePage(invalidTemplate, this, loaders, templateRenderer));
666 pageToadletRegistry.addPage(new EditImagePage(emptyTemplate, this, loaders, templateRenderer));
667 pageToadletRegistry.addPage(new DeleteImagePage(deleteImageTemplate, this, loaders, templateRenderer));
668 pageToadletRegistry.addPage(new TrustPage(emptyTemplate, this, loaders, templateRenderer));
669 pageToadletRegistry.addPage(new DistrustPage(emptyTemplate, this, loaders, templateRenderer));
670 pageToadletRegistry.addPage(new UntrustPage(emptyTemplate, this, loaders, templateRenderer));
671 pageToadletRegistry.addPage(new MarkAsKnownPage(emptyTemplate, this, loaders, templateRenderer));
672 pageToadletRegistry.addPage(new BookmarkPage(emptyTemplate, this, loaders, templateRenderer));
673 pageToadletRegistry.addPage(new UnbookmarkPage(emptyTemplate, this, loaders, templateRenderer));
674 pageToadletRegistry.addPage(new BookmarksPage(bookmarksTemplate, this, loaders, templateRenderer));
675 pageToadletRegistry.addPage(new SearchPage(searchTemplate, this, loaders, templateRenderer));
676 pageToadletRegistry.addPage(new DeleteSonePage(deleteSoneTemplate, this, loaders, templateRenderer));
677 pageToadletRegistry.addPage(new LoginPage(loginTemplate, this, loaders, templateRenderer));
678 pageToadletRegistry.addPage(new LogoutPage(emptyTemplate, this, loaders, templateRenderer));
679 pageToadletRegistry.addPage(new OptionsPage(optionsTemplate, this, loaders, templateRenderer));
680 pageToadletRegistry.addPage(new RescuePage(rescueTemplate, this, loaders, templateRenderer));
681 pageToadletRegistry.addPage(new AboutPage(aboutTemplate, this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
682 pageToadletRegistry.addPage(new SoneTemplatePage("noPermission.html", this, loaders, noPermissionTemplate, templateRenderer, "Page.NoPermission.Title"));
683 pageToadletRegistry.addPage(new SoneTemplatePage("emptyImageTitle.html", this, loaders, emptyImageTitleTemplate, templateRenderer, "Page.EmptyImageTitle.Title"));
684 pageToadletRegistry.addPage(new SoneTemplatePage("emptyAlbumTitle.html", this, loaders, emptyAlbumTitleTemplate, templateRenderer, "Page.EmptyAlbumTitle.Title"));
685 pageToadletRegistry.addPage(new DismissNotificationPage(emptyTemplate, this, loaders, templateRenderer));
686 pageToadletRegistry.addPage(new SoneTemplatePage("invalid.html", this, loaders, invalidTemplate, templateRenderer, "Page.Invalid.Title"));
687 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
688 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
689 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
690 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
691 pageToadletRegistry.addPage(new GetImagePage(this));
692 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
693 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
694 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
695 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
696 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
697 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
698 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
699 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
700 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
701 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
702 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
703 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
704 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
705 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
706 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
707 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
708 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
709 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
710 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
711 pageToadletRegistry.addPage(new TrustAjaxPage(this));
712 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
713 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
714 pageToadletRegistry.addPage(new LikeAjaxPage(this));
715 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
716 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
717 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
718 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
719 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
720 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
721 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
723 pageToadletRegistry.registerToadlets();
727 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
728 * {@link SonePart}s in the given text (after parsing it using
729 * {@link SoneTextParser}).
733 * @return All mentioned local Sones
735 private Collection<Sone> getMentionedSones(String text) {
736 /* we need no context to find mentioned Sones. */
737 Set<Sone> mentionedSones = new HashSet<>();
738 for (Part part : soneTextParser.parse(text, null)) {
739 if (part instanceof SonePart) {
740 mentionedSones.add(((SonePart) part).getSone());
743 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
747 * Returns the Sone insert notification for the given Sone. If no
748 * notification for the given Sone exists, a new notification is created and
752 * The Sone to get the insert notification for
753 * @return The Sone insert notification
755 private TemplateNotification getSoneInsertNotification(Sone sone) {
756 synchronized (soneInsertNotifications) {
757 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
758 if (templateNotification == null) {
759 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
760 templateNotification.set("insertSone", sone);
761 soneInsertNotifications.put(sone, templateNotification);
763 return templateNotification;
767 private boolean localSoneMentionedInNewPostOrReply(Post post) {
768 if (!post.getSone().isLocal()) {
769 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
773 for (PostReply postReply : getCore().getReplies(post.getId())) {
774 if (postReply.getSone().isLocal()) {
777 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
789 * Notifies the web interface that a new {@link Sone} was found.
791 * @param newSoneFoundEvent
795 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
796 newSoneNotification.add(newSoneFoundEvent.sone());
797 if (!hasFirstStartNotification()) {
798 notificationManager.addNotification(newSoneNotification);
803 * Notifies the web interface that a new {@link Post} was found.
805 * @param newPostFoundEvent
809 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
810 Post post = newPostFoundEvent.post();
811 boolean isLocal = post.getSone().isLocal();
813 localPostNotification.add(post);
815 newPostNotification.add(post);
817 if (!hasFirstStartNotification()) {
818 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
819 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
820 mentionNotification.add(post);
821 notificationManager.addNotification(mentionNotification);
824 getCore().markPostKnown(post);
829 * Notifies the web interface that a new {@link PostReply} was found.
831 * @param newPostReplyFoundEvent
835 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
836 PostReply reply = newPostReplyFoundEvent.postReply();
837 boolean isLocal = reply.getSone().isLocal();
839 localReplyNotification.add(reply);
841 newReplyNotification.add(reply);
843 if (!hasFirstStartNotification()) {
844 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
845 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
846 mentionNotification.add(reply.getPost().get());
847 notificationManager.addNotification(mentionNotification);
850 getCore().markReplyKnown(reply);
855 * Notifies the web interface that a {@link Sone} was marked as known.
857 * @param markSoneKnownEvent
861 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
862 newSoneNotification.remove(markSoneKnownEvent.sone());
866 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
867 removePost(markPostKnownEvent.post());
871 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
872 removeReply(markPostReplyKnownEvent.postReply());
876 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
877 newSoneNotification.remove(soneRemovedEvent.sone());
881 public void postRemoved(PostRemovedEvent postRemovedEvent) {
882 removePost(postRemovedEvent.post());
885 private void removePost(Post post) {
886 newPostNotification.remove(post);
887 localPostNotification.remove(post);
888 if (!localSoneMentionedInNewPostOrReply(post)) {
889 mentionNotification.remove(post);
894 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
895 removeReply(postReplyRemovedEvent.postReply());
898 private void removeReply(PostReply reply) {
899 newReplyNotification.remove(reply);
900 localReplyNotification.remove(reply);
901 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
902 mentionNotification.remove(reply.getPost().get());
907 * Notifies the web interface that a Sone was locked.
909 * @param soneLockedEvent
913 public void soneLocked(SoneLockedEvent soneLockedEvent) {
914 final Sone sone = soneLockedEvent.sone();
915 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
918 @SuppressWarnings("synthetic-access")
920 lockedSonesNotification.add(sone);
921 notificationManager.addNotification(lockedSonesNotification);
923 }, 5, TimeUnit.MINUTES);
924 lockedSonesTickerObjects.put(sone, tickerObject);
928 * Notifies the web interface that a Sone was unlocked.
930 * @param soneUnlockedEvent
934 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
935 lockedSonesNotification.remove(soneUnlockedEvent.sone());
936 lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
940 * Notifies the web interface that a {@link Sone} is being inserted.
942 * @param soneInsertingEvent
946 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
947 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.sone());
948 soneInsertNotification.set("soneStatus", "inserting");
949 if (soneInsertingEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
950 notificationManager.addNotification(soneInsertNotification);
955 * Notifies the web interface that a {@link Sone} was inserted.
957 * @param soneInsertedEvent
961 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
962 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.sone());
963 soneInsertNotification.set("soneStatus", "inserted");
964 soneInsertNotification.set("insertDuration", soneInsertedEvent.insertDuration() / 1000);
965 if (soneInsertedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
966 notificationManager.addNotification(soneInsertNotification);
971 * Notifies the web interface that a {@link Sone} insert was aborted.
973 * @param soneInsertAbortedEvent
977 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
978 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.sone());
979 soneInsertNotification.set("soneStatus", "insert-aborted");
980 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.cause());
981 if (soneInsertAbortedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
982 notificationManager.addNotification(soneInsertNotification);
987 * Notifies the web interface that a new Sone version was found.
989 * @param updateFoundEvent
993 public void updateFound(UpdateFoundEvent updateFoundEvent) {
994 newVersionNotification.set("latestVersion", updateFoundEvent.version());
995 newVersionNotification.set("latestEdition", updateFoundEvent.latestEdition());
996 newVersionNotification.set("releaseTime", updateFoundEvent.releaseTime());
997 newVersionNotification.set("disruptive", updateFoundEvent.disruptive());
998 notificationManager.addNotification(newVersionNotification);
1002 * Notifies the web interface that an image insert was started
1004 * @param imageInsertStartedEvent
1008 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
1009 insertingImagesNotification.add(imageInsertStartedEvent.image());
1010 notificationManager.addNotification(insertingImagesNotification);
1014 * Notifies the web interface that an {@link Image} insert was aborted.
1016 * @param imageInsertAbortedEvent
1020 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
1021 insertingImagesNotification.remove(imageInsertAbortedEvent.image());
1025 * Notifies the web interface that an {@link Image} insert is finished.
1027 * @param imageInsertFinishedEvent
1031 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
1032 insertingImagesNotification.remove(imageInsertFinishedEvent.image());
1033 insertedImagesNotification.add(imageInsertFinishedEvent.image());
1034 notificationManager.addNotification(insertedImagesNotification);
1038 * Notifies the web interface that an {@link Image} insert has failed.
1040 * @param imageInsertFailedEvent
1044 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
1045 insertingImagesNotification.remove(imageInsertFailedEvent.image());
1046 imageInsertFailedNotification.add(imageInsertFailedEvent.image());
1047 notificationManager.addNotification(imageInsertFailedNotification);