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.pages.AboutPage;
116 import net.pterodactylus.sone.web.pages.BookmarkPage;
117 import net.pterodactylus.sone.web.pages.BookmarksPage;
118 import net.pterodactylus.sone.web.pages.CreateAlbumPage;
119 import net.pterodactylus.sone.web.pages.CreatePostPage;
120 import net.pterodactylus.sone.web.pages.CreateReplyPage;
121 import net.pterodactylus.sone.web.pages.CreateSonePage;
122 import net.pterodactylus.sone.web.pages.DeleteAlbumPage;
123 import net.pterodactylus.sone.web.pages.DeleteImagePage;
124 import net.pterodactylus.sone.web.pages.DeletePostPage;
125 import net.pterodactylus.sone.web.pages.DeleteProfileFieldPage;
126 import net.pterodactylus.sone.web.pages.DeleteReplyPage;
127 import net.pterodactylus.sone.web.pages.DeleteSonePage;
128 import net.pterodactylus.sone.web.pages.DismissNotificationPage;
129 import net.pterodactylus.sone.web.pages.DistrustPage;
130 import net.pterodactylus.sone.web.pages.EditAlbumPage;
131 import net.pterodactylus.sone.web.pages.EditImagePage;
132 import net.pterodactylus.sone.web.pages.EditProfileFieldPage;
133 import net.pterodactylus.sone.web.pages.EditProfilePage;
134 import net.pterodactylus.sone.web.pages.FollowSonePage;
135 import net.pterodactylus.sone.web.pages.GetImagePage;
136 import net.pterodactylus.sone.web.pages.ImageBrowserPage;
137 import net.pterodactylus.sone.web.pages.IndexPage;
138 import net.pterodactylus.sone.web.pages.KnownSonesPage;
139 import net.pterodactylus.sone.web.pages.LikePage;
140 import net.pterodactylus.sone.web.pages.LockSonePage;
141 import net.pterodactylus.sone.web.pages.LoginPage;
142 import net.pterodactylus.sone.web.pages.LogoutPage;
143 import net.pterodactylus.sone.web.pages.MarkAsKnownPage;
144 import net.pterodactylus.sone.web.pages.NewPage;
145 import net.pterodactylus.sone.web.pages.OptionsPage;
146 import net.pterodactylus.sone.web.pages.RescuePage;
147 import net.pterodactylus.sone.web.pages.SearchPage;
148 import net.pterodactylus.sone.web.pages.SoneTemplatePage;
149 import net.pterodactylus.sone.web.pages.TrustPage;
150 import net.pterodactylus.sone.web.pages.UnbookmarkPage;
151 import net.pterodactylus.sone.web.pages.UnfollowSonePage;
152 import net.pterodactylus.sone.web.pages.UnlikePage;
153 import net.pterodactylus.sone.web.pages.UnlockSonePage;
154 import net.pterodactylus.sone.web.pages.UntrustPage;
155 import net.pterodactylus.sone.web.pages.UploadImagePage;
156 import net.pterodactylus.sone.web.pages.ViewPostPage;
157 import net.pterodactylus.sone.web.pages.ViewSonePage;
158 import net.pterodactylus.util.notify.Notification;
159 import net.pterodactylus.util.notify.NotificationManager;
160 import net.pterodactylus.util.notify.TemplateNotification;
161 import net.pterodactylus.util.template.Template;
162 import net.pterodactylus.util.template.TemplateContextFactory;
163 import net.pterodactylus.util.web.RedirectPage;
164 import net.pterodactylus.util.web.TemplatePage;
166 import freenet.clients.http.SessionManager;
167 import freenet.clients.http.SessionManager.Session;
168 import freenet.clients.http.ToadletContext;
169 import freenet.l10n.BaseL10n;
171 import com.google.common.base.Optional;
172 import com.google.common.collect.Collections2;
173 import com.google.common.collect.ImmutableSet;
174 import com.google.common.eventbus.Subscribe;
175 import com.google.inject.Inject;
178 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
179 * references to l10n helpers.
181 public class WebInterface implements SessionProvider {
184 private static final Logger logger = getLogger(WebInterface.class.getName());
186 /** The loaders for templates, pages, and classpath providers. */
187 private final Loaders loaders;
189 /** The notification manager. */
190 private final NotificationManager notificationManager = new NotificationManager();
192 /** The Sone plugin. */
193 private final SonePlugin sonePlugin;
195 /** The form password. */
196 private final String formPassword;
198 /** The template context factory. */
199 private final TemplateContextFactory templateContextFactory;
201 /** The Sone text parser. */
202 private final SoneTextParser soneTextParser;
204 /** The parser filter. */
205 private final ParserFilter parserFilter;
206 private final ShortenFilter shortenFilter;
207 private final RenderFilter renderFilter;
209 private final ListNotificationFilter listNotificationFilter;
210 private final PostVisibilityFilter postVisibilityFilter;
211 private final ReplyVisibilityFilter replyVisibilityFilter;
213 private final ElementLoader elementLoader;
214 private final LinkedElementRenderFilter linkedElementRenderFilter;
215 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
216 private final L10nFilter l10nFilter;
218 private final PageToadletRegistry pageToadletRegistry;
220 /** The “new Sone” notification. */
221 private final ListNotification<Sone> newSoneNotification;
223 /** The “new post” notification. */
224 private final ListNotification<Post> newPostNotification;
226 /** The “new reply” notification. */
227 private final ListNotification<PostReply> newReplyNotification;
229 /** The invisible “local post” notification. */
230 private final ListNotification<Post> localPostNotification;
232 /** The invisible “local reply” notification. */
233 private final ListNotification<PostReply> localReplyNotification;
235 /** The “you have been mentioned” notification. */
236 private final ListNotification<Post> mentionNotification;
238 /** Notifications for sone inserts. */
239 private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<>();
241 /** Sone locked notification ticker objects. */
242 private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
244 /** The “Sone locked” notification. */
245 private final ListNotification<Sone> lockedSonesNotification;
247 /** The “new version” notification. */
248 private final TemplateNotification newVersionNotification;
250 /** The “inserting images” notification. */
251 private final ListNotification<Image> insertingImagesNotification;
253 /** The “inserted images” notification. */
254 private final ListNotification<Image> insertedImagesNotification;
256 /** The “image insert failed” notification. */
257 private final ListNotification<Image> imageInsertFailedNotification;
259 /** Scheduled executor for time-based notifications. */
260 private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
263 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
264 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
265 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
266 ParserFilter parserFilter, ShortenFilter shortenFilter,
267 RenderFilter renderFilter,
268 LinkedElementRenderFilter linkedElementRenderFilter,
269 PageToadletRegistry pageToadletRegistry) {
270 this.sonePlugin = sonePlugin;
271 this.loaders = loaders;
272 this.listNotificationFilter = listNotificationFilter;
273 this.postVisibilityFilter = postVisibilityFilter;
274 this.replyVisibilityFilter = replyVisibilityFilter;
275 this.elementLoader = elementLoader;
276 this.parserFilter = parserFilter;
277 this.shortenFilter = shortenFilter;
278 this.renderFilter = renderFilter;
279 this.linkedElementRenderFilter = linkedElementRenderFilter;
280 this.pageToadletRegistry = pageToadletRegistry;
281 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
282 soneTextParser = new SoneTextParser(getCore(), getCore());
283 l10nFilter = new L10nFilter(getL10n());
285 this.templateContextFactory = templateContextFactory;
286 templateContextFactory.addTemplateObject("webInterface", this);
287 templateContextFactory.addTemplateObject("formPassword", formPassword);
289 /* create notifications. */
290 Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html");
291 newSoneNotification = new ListNotification<>("new-sone-notification", "sones", newSoneNotificationTemplate, false);
293 Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
294 newPostNotification = new ListNotification<>("new-post-notification", "posts", newPostNotificationTemplate, false);
296 Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html");
297 localPostNotification = new ListNotification<>("local-post-notification", "posts", localPostNotificationTemplate, false);
299 Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
300 newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
302 Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html");
303 localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
305 Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html");
306 mentionNotification = new ListNotification<>("mention-notification", "posts", mentionNotificationTemplate, false);
308 Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html");
309 lockedSonesNotification = new ListNotification<>("sones-locked-notification", "sones", lockedSonesTemplate);
311 Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html");
312 newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
314 Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html");
315 insertingImagesNotification = new ListNotification<>("inserting-images-notification", "images", insertingImagesTemplate);
317 Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html");
318 insertedImagesNotification = new ListNotification<>("inserted-images-notification", "images", insertedImagesTemplate);
320 Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html");
321 imageInsertFailedNotification = new ListNotification<>("image-insert-failed-notification", "images", imageInsertFailedTemplate);
329 * Returns the Sone core used by the Sone plugin.
331 * @return The Sone core
334 public Core getCore() {
335 return sonePlugin.core();
339 * Returns the template context factory of the web interface.
341 * @return The template context factory
343 public TemplateContextFactory getTemplateContextFactory() {
344 return templateContextFactory;
347 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
348 return getSessionManager().useSession(toadletContenxt);
351 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
352 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
353 if (session == null) {
354 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
359 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
360 Collection<Sone> localSones = getCore().getLocalSones();
361 if (localSones.size() == 1) {
362 return localSones.iterator().next();
364 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
367 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
368 Collection<Sone> localSones = getCore().getLocalSones();
369 if (localSones.size() == 1) {
370 return localSones.iterator().next();
372 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
376 * Returns the currently logged in Sone.
380 * @return The currently logged in Sone, or {@code null} if no Sone is
381 * currently logged in
383 private Sone getCurrentSone(Session session) {
384 if (session == null) {
387 String soneId = (String) session.getAttribute("Sone.CurrentSone");
388 if (soneId == null) {
391 return getCore().getLocalSone(soneId);
396 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
397 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
401 * Sets the currently logged in Sone.
403 * @param toadletContext
404 * The toadlet context
406 * The Sone to set as currently logged in
409 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
410 Session session = getOrCreateCurrentSession(toadletContext);
412 session.removeAttribute("Sone.CurrentSone");
414 session.setAttribute("Sone.CurrentSone", sone.getId());
419 * Returns the notification manager.
421 * @return The notification manager
423 public NotificationManager getNotifications() {
424 return notificationManager;
428 public Optional<Notification> getNotification(@Nonnull String notificationId) {
429 return Optional.fromNullable(notificationManager.getNotification(notificationId));
433 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
434 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
438 * Returns the l10n helper of the node.
440 * @return The node’s l10n helper
442 public BaseL10n getL10n() {
443 return sonePlugin.l10n().getBase();
447 * Returns the session manager of the node.
449 * @return The node’s session manager
451 public SessionManager getSessionManager() {
452 return sonePlugin.pluginRespirator().getSessionManager("Sone");
456 * Returns the node’s form password.
458 * @return The form password
460 public String getFormPassword() {
465 * Returns the posts that have been announced as new in the
466 * {@link #newPostNotification}.
468 * @return The new posts
470 public Set<Post> getNewPosts() {
471 return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
475 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
476 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
477 .addAll(newPostNotification.getElements())
478 .addAll(localPostNotification.getElements())
480 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
484 * Returns the replies that have been announced as new in the
485 * {@link #newReplyNotification}.
487 * @return The new replies
489 public Set<PostReply> getNewReplies() {
490 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
494 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
495 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
496 .addAll(newReplyNotification.getElements())
497 .addAll(localReplyNotification.getElements())
499 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
503 * Sets whether the current start of the plugin is the first start. It is
504 * considered a first start if the configuration file does not exist.
507 * {@code true} if no configuration file existed when Sone was
508 * loaded, {@code false} otherwise
510 public void setFirstStart(boolean firstStart) {
512 Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html");
513 Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
514 notificationManager.addNotification(firstStartNotification);
519 * Sets whether Sone was started with a fresh configuration file.
522 * {@code true} if Sone was started with a fresh configuration,
523 * {@code false} if the existing configuration could be read
525 public void setNewConfig(boolean newConfig) {
526 if (newConfig && !hasFirstStartNotification()) {
527 Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html");
528 Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
529 notificationManager.addNotification(configNotReadNotification);
538 * Returns whether the first start notification is currently displayed.
540 * @return {@code true} if the first-start notification is currently
541 * displayed, {@code false} otherwise
543 private boolean hasFirstStartNotification() {
544 return notificationManager.getNotification("first-start-notification") != null;
552 * Starts the web interface and registers all toadlets.
554 public void start() {
557 /* notification templates. */
558 Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html");
560 final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
561 notificationManager.addNotification(startupNotification);
563 ticker.schedule(new Runnable() {
567 startupNotification.dismiss();
569 }, 2, TimeUnit.MINUTES);
571 Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html");
572 final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
573 ticker.scheduleAtFixedRate(new Runnable() {
576 @SuppressWarnings("synthetic-access")
578 if (getCore().getIdentityManager().isConnected()) {
579 wotMissingNotification.dismiss();
581 notificationManager.addNotification(wotMissingNotification);
585 }, 15, 15, TimeUnit.SECONDS);
589 * Stops the web interface and unregisters all toadlets.
592 pageToadletRegistry.unregisterToadlets();
593 ticker.shutdownNow();
601 * Register all toadlets.
603 private void registerToadlets() {
604 Template emptyTemplate = parse(new StringReader(""));
605 Template loginTemplate = loaders.loadTemplate("/templates/login.html");
606 Template indexTemplate = loaders.loadTemplate("/templates/index.html");
607 Template newTemplate = loaders.loadTemplate("/templates/new.html");
608 Template knownSonesTemplate = loaders.loadTemplate("/templates/knownSones.html");
609 Template createSoneTemplate = loaders.loadTemplate("/templates/createSone.html");
610 Template createPostTemplate = loaders.loadTemplate("/templates/createPost.html");
611 Template createReplyTemplate = loaders.loadTemplate("/templates/createReply.html");
612 Template bookmarksTemplate = loaders.loadTemplate("/templates/bookmarks.html");
613 Template searchTemplate = loaders.loadTemplate("/templates/search.html");
614 Template editProfileTemplate = loaders.loadTemplate("/templates/editProfile.html");
615 Template editProfileFieldTemplate = loaders.loadTemplate("/templates/editProfileField.html");
616 Template deleteProfileFieldTemplate = loaders.loadTemplate("/templates/deleteProfileField.html");
617 Template viewSoneTemplate = loaders.loadTemplate("/templates/viewSone.html");
618 Template viewPostTemplate = loaders.loadTemplate("/templates/viewPost.html");
619 Template deletePostTemplate = loaders.loadTemplate("/templates/deletePost.html");
620 Template deleteReplyTemplate = loaders.loadTemplate("/templates/deleteReply.html");
621 Template deleteSoneTemplate = loaders.loadTemplate("/templates/deleteSone.html");
622 Template imageBrowserTemplate = loaders.loadTemplate("/templates/imageBrowser.html");
623 Template createAlbumTemplate = loaders.loadTemplate("/templates/createAlbum.html");
624 Template deleteAlbumTemplate = loaders.loadTemplate("/templates/deleteAlbum.html");
625 Template deleteImageTemplate = loaders.loadTemplate("/templates/deleteImage.html");
626 Template noPermissionTemplate = loaders.loadTemplate("/templates/noPermission.html");
627 Template emptyImageTitleTemplate = loaders.loadTemplate("/templates/emptyImageTitle.html");
628 Template emptyAlbumTitleTemplate = loaders.loadTemplate("/templates/emptyAlbumTitle.html");
629 Template optionsTemplate = loaders.loadTemplate("/templates/options.html");
630 Template rescueTemplate = loaders.loadTemplate("/templates/rescue.html");
631 Template aboutTemplate = loaders.loadTemplate("/templates/about.html");
632 Template invalidTemplate = loaders.loadTemplate("/templates/invalid.html");
633 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
634 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
635 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
637 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
638 pageToadletRegistry.addPage(new IndexPage(indexTemplate, this, loaders, postVisibilityFilter));
639 pageToadletRegistry.addPage(new NewPage(newTemplate, this, loaders));
640 pageToadletRegistry.addPage(new CreateSonePage(createSoneTemplate, this, loaders));
641 pageToadletRegistry.addPage(new KnownSonesPage(knownSonesTemplate, this, loaders));
642 pageToadletRegistry.addPage(new EditProfilePage(editProfileTemplate, this, loaders));
643 pageToadletRegistry.addPage(new EditProfileFieldPage(editProfileFieldTemplate, this, loaders));
644 pageToadletRegistry.addPage(new DeleteProfileFieldPage(deleteProfileFieldTemplate, this, loaders));
645 pageToadletRegistry.addPage(new CreatePostPage(createPostTemplate, this, loaders));
646 pageToadletRegistry.addPage(new CreateReplyPage(createReplyTemplate, this, loaders));
647 pageToadletRegistry.addPage(new ViewSonePage(viewSoneTemplate, this, loaders));
648 pageToadletRegistry.addPage(new ViewPostPage(viewPostTemplate, this, loaders));
649 pageToadletRegistry.addPage(new LikePage(emptyTemplate, this, loaders));
650 pageToadletRegistry.addPage(new UnlikePage(emptyTemplate, this, loaders));
651 pageToadletRegistry.addPage(new DeletePostPage(deletePostTemplate, this, loaders));
652 pageToadletRegistry.addPage(new DeleteReplyPage(deleteReplyTemplate, this, loaders));
653 pageToadletRegistry.addPage(new LockSonePage(emptyTemplate, this, loaders));
654 pageToadletRegistry.addPage(new UnlockSonePage(emptyTemplate, this, loaders));
655 pageToadletRegistry.addPage(new FollowSonePage(emptyTemplate, this, loaders));
656 pageToadletRegistry.addPage(new UnfollowSonePage(emptyTemplate, this, loaders));
657 pageToadletRegistry.addPage(new ImageBrowserPage(imageBrowserTemplate, this, loaders));
658 pageToadletRegistry.addPage(new CreateAlbumPage(createAlbumTemplate, this, loaders));
659 pageToadletRegistry.addPage(new EditAlbumPage(emptyTemplate, this, loaders));
660 pageToadletRegistry.addPage(new DeleteAlbumPage(deleteAlbumTemplate, this, loaders));
661 pageToadletRegistry.addPage(new UploadImagePage(invalidTemplate, this, loaders));
662 pageToadletRegistry.addPage(new EditImagePage(emptyTemplate, this, loaders));
663 pageToadletRegistry.addPage(new DeleteImagePage(deleteImageTemplate, this, loaders));
664 pageToadletRegistry.addPage(new TrustPage(emptyTemplate, this, loaders));
665 pageToadletRegistry.addPage(new DistrustPage(emptyTemplate, this, loaders));
666 pageToadletRegistry.addPage(new UntrustPage(emptyTemplate, this, loaders));
667 pageToadletRegistry.addPage(new MarkAsKnownPage(emptyTemplate, this, loaders));
668 pageToadletRegistry.addPage(new BookmarkPage(emptyTemplate, this, loaders));
669 pageToadletRegistry.addPage(new UnbookmarkPage(emptyTemplate, this, loaders));
670 pageToadletRegistry.addPage(new BookmarksPage(bookmarksTemplate, this, loaders));
671 pageToadletRegistry.addPage(new SearchPage(searchTemplate, this, loaders));
672 pageToadletRegistry.addPage(new DeleteSonePage(deleteSoneTemplate, this, loaders));
673 pageToadletRegistry.addPage(new LoginPage(loginTemplate, this, loaders));
674 pageToadletRegistry.addPage(new LogoutPage(emptyTemplate, this, loaders));
675 pageToadletRegistry.addPage(new OptionsPage(optionsTemplate, this, loaders));
676 pageToadletRegistry.addPage(new RescuePage(rescueTemplate, this, loaders));
677 pageToadletRegistry.addPage(new AboutPage(aboutTemplate, this, loaders, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
678 pageToadletRegistry.addPage(new SoneTemplatePage("noPermission.html", this, loaders, noPermissionTemplate, "Page.NoPermission.Title"));
679 pageToadletRegistry.addPage(new SoneTemplatePage("emptyImageTitle.html", this, loaders, emptyImageTitleTemplate, "Page.EmptyImageTitle.Title"));
680 pageToadletRegistry.addPage(new SoneTemplatePage("emptyAlbumTitle.html", this, loaders, emptyAlbumTitleTemplate, "Page.EmptyAlbumTitle.Title"));
681 pageToadletRegistry.addPage(new DismissNotificationPage(emptyTemplate, this, loaders));
682 pageToadletRegistry.addPage(new SoneTemplatePage("invalid.html", this, loaders, invalidTemplate, "Page.Invalid.Title"));
683 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
684 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
685 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
686 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
687 pageToadletRegistry.addPage(new GetImagePage(this));
688 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
689 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
690 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
691 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
692 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
693 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
694 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
695 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
696 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
697 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
698 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
699 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
700 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
701 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
702 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
703 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
704 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
705 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
706 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
707 pageToadletRegistry.addPage(new TrustAjaxPage(this));
708 pageToadletRegistry.addPage(new DistrustAjaxPage(this));
709 pageToadletRegistry.addPage(new UntrustAjaxPage(this));
710 pageToadletRegistry.addPage(new LikeAjaxPage(this));
711 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
712 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
713 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
714 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
715 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
716 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
717 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
719 pageToadletRegistry.registerToadlets();
723 * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
724 * {@link SonePart}s in the given text (after parsing it using
725 * {@link SoneTextParser}).
729 * @return All mentioned local Sones
731 private Collection<Sone> getMentionedSones(String text) {
732 /* we need no context to find mentioned Sones. */
733 Set<Sone> mentionedSones = new HashSet<>();
734 for (Part part : soneTextParser.parse(text, null)) {
735 if (part instanceof SonePart) {
736 mentionedSones.add(((SonePart) part).getSone());
739 return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
743 * Returns the Sone insert notification for the given Sone. If no
744 * notification for the given Sone exists, a new notification is created and
748 * The Sone to get the insert notification for
749 * @return The Sone insert notification
751 private TemplateNotification getSoneInsertNotification(Sone sone) {
752 synchronized (soneInsertNotifications) {
753 TemplateNotification templateNotification = soneInsertNotifications.get(sone);
754 if (templateNotification == null) {
755 templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html"));
756 templateNotification.set("insertSone", sone);
757 soneInsertNotifications.put(sone, templateNotification);
759 return templateNotification;
763 private boolean localSoneMentionedInNewPostOrReply(Post post) {
764 if (!post.getSone().isLocal()) {
765 if (!getMentionedSones(post.getText()).isEmpty() && !post.isKnown()) {
769 for (PostReply postReply : getCore().getReplies(post.getId())) {
770 if (postReply.getSone().isLocal()) {
773 if (!getMentionedSones(postReply.getText()).isEmpty() && !postReply.isKnown()) {
785 * Notifies the web interface that a new {@link Sone} was found.
787 * @param newSoneFoundEvent
791 public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
792 newSoneNotification.add(newSoneFoundEvent.sone());
793 if (!hasFirstStartNotification()) {
794 notificationManager.addNotification(newSoneNotification);
799 * Notifies the web interface that a new {@link Post} was found.
801 * @param newPostFoundEvent
805 public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
806 Post post = newPostFoundEvent.post();
807 boolean isLocal = post.getSone().isLocal();
809 localPostNotification.add(post);
811 newPostNotification.add(post);
813 if (!hasFirstStartNotification()) {
814 notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
815 if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
816 mentionNotification.add(post);
817 notificationManager.addNotification(mentionNotification);
820 getCore().markPostKnown(post);
825 * Notifies the web interface that a new {@link PostReply} was found.
827 * @param newPostReplyFoundEvent
831 public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
832 PostReply reply = newPostReplyFoundEvent.postReply();
833 boolean isLocal = reply.getSone().isLocal();
835 localReplyNotification.add(reply);
837 newReplyNotification.add(reply);
839 if (!hasFirstStartNotification()) {
840 notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
841 if (reply.getPost().isPresent() && localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
842 mentionNotification.add(reply.getPost().get());
843 notificationManager.addNotification(mentionNotification);
846 getCore().markReplyKnown(reply);
851 * Notifies the web interface that a {@link Sone} was marked as known.
853 * @param markSoneKnownEvent
857 public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
858 newSoneNotification.remove(markSoneKnownEvent.sone());
862 public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
863 removePost(markPostKnownEvent.post());
867 public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
868 removeReply(markPostReplyKnownEvent.postReply());
872 public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
873 newSoneNotification.remove(soneRemovedEvent.sone());
877 public void postRemoved(PostRemovedEvent postRemovedEvent) {
878 removePost(postRemovedEvent.post());
881 private void removePost(Post post) {
882 newPostNotification.remove(post);
883 localPostNotification.remove(post);
884 if (!localSoneMentionedInNewPostOrReply(post)) {
885 mentionNotification.remove(post);
890 public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
891 removeReply(postReplyRemovedEvent.postReply());
894 private void removeReply(PostReply reply) {
895 newReplyNotification.remove(reply);
896 localReplyNotification.remove(reply);
897 if (reply.getPost().isPresent() && !localSoneMentionedInNewPostOrReply(reply.getPost().get())) {
898 mentionNotification.remove(reply.getPost().get());
903 * Notifies the web interface that a Sone was locked.
905 * @param soneLockedEvent
909 public void soneLocked(SoneLockedEvent soneLockedEvent) {
910 final Sone sone = soneLockedEvent.sone();
911 ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
914 @SuppressWarnings("synthetic-access")
916 lockedSonesNotification.add(sone);
917 notificationManager.addNotification(lockedSonesNotification);
919 }, 5, TimeUnit.MINUTES);
920 lockedSonesTickerObjects.put(sone, tickerObject);
924 * Notifies the web interface that a Sone was unlocked.
926 * @param soneUnlockedEvent
930 public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
931 lockedSonesNotification.remove(soneUnlockedEvent.sone());
932 lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
936 * Notifies the web interface that a {@link Sone} is being inserted.
938 * @param soneInsertingEvent
942 public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
943 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.sone());
944 soneInsertNotification.set("soneStatus", "inserting");
945 if (soneInsertingEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
946 notificationManager.addNotification(soneInsertNotification);
951 * Notifies the web interface that a {@link Sone} was inserted.
953 * @param soneInsertedEvent
957 public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
958 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.sone());
959 soneInsertNotification.set("soneStatus", "inserted");
960 soneInsertNotification.set("insertDuration", soneInsertedEvent.insertDuration() / 1000);
961 if (soneInsertedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
962 notificationManager.addNotification(soneInsertNotification);
967 * Notifies the web interface that a {@link Sone} insert was aborted.
969 * @param soneInsertAbortedEvent
973 public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
974 TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.sone());
975 soneInsertNotification.set("soneStatus", "insert-aborted");
976 soneInsertNotification.set("insert-error", soneInsertAbortedEvent.cause());
977 if (soneInsertAbortedEvent.sone().getOptions().isSoneInsertNotificationEnabled()) {
978 notificationManager.addNotification(soneInsertNotification);
983 * Notifies the web interface that a new Sone version was found.
985 * @param updateFoundEvent
989 public void updateFound(UpdateFoundEvent updateFoundEvent) {
990 newVersionNotification.set("latestVersion", updateFoundEvent.version());
991 newVersionNotification.set("latestEdition", updateFoundEvent.latestEdition());
992 newVersionNotification.set("releaseTime", updateFoundEvent.releaseTime());
993 newVersionNotification.set("disruptive", updateFoundEvent.disruptive());
994 notificationManager.addNotification(newVersionNotification);
998 * Notifies the web interface that an image insert was started
1000 * @param imageInsertStartedEvent
1004 public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
1005 insertingImagesNotification.add(imageInsertStartedEvent.image());
1006 notificationManager.addNotification(insertingImagesNotification);
1010 * Notifies the web interface that an {@link Image} insert was aborted.
1012 * @param imageInsertAbortedEvent
1016 public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
1017 insertingImagesNotification.remove(imageInsertAbortedEvent.image());
1021 * Notifies the web interface that an {@link Image} insert is finished.
1023 * @param imageInsertFinishedEvent
1027 public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
1028 insertingImagesNotification.remove(imageInsertFinishedEvent.image());
1029 insertedImagesNotification.add(imageInsertFinishedEvent.image());
1030 notificationManager.addNotification(insertedImagesNotification);
1034 * Notifies the web interface that an {@link Image} insert has failed.
1036 * @param imageInsertFailedEvent
1040 public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
1041 insertingImagesNotification.remove(imageInsertFailedEvent.image());
1042 imageInsertFailedNotification.add(imageInsertFailedEvent.image());
1043 notificationManager.addNotification(imageInsertFailedNotification);