2 * Sone - WebInterface.java - Copyright © 2010–2020 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;
23 import java.util.Collection;
25 import java.util.TimeZone;
26 import java.util.UUID;
27 import java.util.logging.Logger;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import javax.inject.Named;
32 import net.pterodactylus.sone.core.Core;
33 import net.pterodactylus.sone.core.ElementLoader;
34 import net.pterodactylus.sone.core.event.*;
35 import net.pterodactylus.sone.data.Post;
36 import net.pterodactylus.sone.data.PostReply;
37 import net.pterodactylus.sone.data.Sone;
38 import net.pterodactylus.sone.freenet.L10nFilter;
39 import net.pterodactylus.sone.freenet.Translation;
40 import net.pterodactylus.sone.main.Loaders;
41 import net.pterodactylus.sone.main.PluginHomepage;
42 import net.pterodactylus.sone.main.PluginVersion;
43 import net.pterodactylus.sone.main.PluginYear;
44 import net.pterodactylus.sone.main.SonePlugin;
45 import net.pterodactylus.sone.notify.ListNotification;
46 import net.pterodactylus.sone.notify.ListNotificationFilter;
47 import net.pterodactylus.sone.notify.PostVisibilityFilter;
48 import net.pterodactylus.sone.notify.ReplyVisibilityFilter;
49 import net.pterodactylus.sone.template.LinkedElementRenderFilter;
50 import net.pterodactylus.sone.template.ParserFilter;
51 import net.pterodactylus.sone.template.RenderFilter;
52 import net.pterodactylus.sone.template.ShortenFilter;
53 import net.pterodactylus.sone.text.TimeTextConverter;
54 import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage;
55 import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage;
56 import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage;
57 import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage;
58 import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage;
59 import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage;
60 import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage;
61 import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage;
62 import net.pterodactylus.sone.web.ajax.EditImageAjaxPage;
63 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
64 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
65 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
66 import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage;
67 import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage;
68 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
69 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
70 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
71 import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage;
72 import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage;
73 import net.pterodactylus.sone.web.ajax.LikeAjaxPage;
74 import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage;
75 import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage;
76 import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage;
77 import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage;
78 import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage;
79 import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage;
80 import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage;
81 import net.pterodactylus.sone.web.page.FreenetRequest;
82 import net.pterodactylus.sone.web.page.TemplateRenderer;
83 import net.pterodactylus.sone.web.pages.*;
84 import net.pterodactylus.util.notify.Notification;
85 import net.pterodactylus.util.notify.NotificationManager;
86 import net.pterodactylus.util.template.Template;
87 import net.pterodactylus.util.template.TemplateContextFactory;
88 import net.pterodactylus.util.web.RedirectPage;
89 import net.pterodactylus.util.web.TemplatePage;
91 import freenet.clients.http.SessionManager;
92 import freenet.clients.http.SessionManager.Session;
93 import freenet.clients.http.ToadletContext;
95 import com.codahale.metrics.*;
96 import com.google.common.base.Optional;
97 import com.google.common.collect.ImmutableSet;
98 import com.google.common.eventbus.Subscribe;
99 import com.google.inject.Inject;
102 * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
103 * references to l10n helpers.
105 public class WebInterface implements SessionProvider {
108 private static final Logger logger = getLogger(WebInterface.class.getName());
110 /** The loaders for templates, pages, and classpath providers. */
111 private final Loaders loaders;
113 /** The notification manager. */
114 private final NotificationManager notificationManager;
116 /** The Sone plugin. */
117 private final SonePlugin sonePlugin;
119 /** The form password. */
120 private final String formPassword;
122 /** The template context factory. */
123 private final TemplateContextFactory templateContextFactory;
124 private final TemplateRenderer templateRenderer;
126 /** The parser filter. */
127 private final ParserFilter parserFilter;
128 private final ShortenFilter shortenFilter;
129 private final RenderFilter renderFilter;
131 private final ListNotificationFilter listNotificationFilter;
132 private final PostVisibilityFilter postVisibilityFilter;
133 private final ReplyVisibilityFilter replyVisibilityFilter;
135 private final ElementLoader elementLoader;
136 private final LinkedElementRenderFilter linkedElementRenderFilter;
137 private final TimeTextConverter timeTextConverter = new TimeTextConverter();
138 private final L10nFilter l10nFilter;
140 private final PageToadletRegistry pageToadletRegistry;
141 private final MetricRegistry metricRegistry;
142 private final Translation translation;
144 /** The “new post” notification. */
145 private final ListNotification<Post> newPostNotification;
147 /** The “new reply” notification. */
148 private final ListNotification<PostReply> newReplyNotification;
150 /** The invisible “local post” notification. */
151 private final ListNotification<Post> localPostNotification;
153 /** The invisible “local reply” notification. */
154 private final ListNotification<PostReply> localReplyNotification;
157 public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter,
158 PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter,
159 ElementLoader elementLoader, TemplateContextFactory templateContextFactory,
160 TemplateRenderer templateRenderer,
161 ParserFilter parserFilter, ShortenFilter shortenFilter,
162 RenderFilter renderFilter,
163 LinkedElementRenderFilter linkedElementRenderFilter,
164 PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter,
165 NotificationManager notificationManager, @Named("newRemotePost") ListNotification<Post> newPostNotification,
166 @Named("newRemotePostReply") ListNotification<PostReply> newReplyNotification,
167 @Named("localPost") ListNotification<Post> localPostNotification,
168 @Named("localReply") ListNotification<PostReply> localReplyNotification) {
169 this.sonePlugin = sonePlugin;
170 this.loaders = loaders;
171 this.listNotificationFilter = listNotificationFilter;
172 this.postVisibilityFilter = postVisibilityFilter;
173 this.replyVisibilityFilter = replyVisibilityFilter;
174 this.elementLoader = elementLoader;
175 this.templateRenderer = templateRenderer;
176 this.parserFilter = parserFilter;
177 this.shortenFilter = shortenFilter;
178 this.renderFilter = renderFilter;
179 this.linkedElementRenderFilter = linkedElementRenderFilter;
180 this.pageToadletRegistry = pageToadletRegistry;
181 this.metricRegistry = metricRegistry;
182 this.l10nFilter = l10nFilter;
183 this.translation = translation;
184 this.notificationManager = notificationManager;
185 this.newPostNotification = newPostNotification;
186 this.newReplyNotification = newReplyNotification;
187 this.localPostNotification = localPostNotification;
188 this.localReplyNotification = localReplyNotification;
189 formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
191 this.templateContextFactory = templateContextFactory;
192 templateContextFactory.addTemplateObject("webInterface", this);
193 templateContextFactory.addTemplateObject("formPassword", formPassword);
201 * Returns the Sone core used by the Sone plugin.
203 * @return The Sone core
206 public Core getCore() {
207 return sonePlugin.core();
211 * Returns the template context factory of the web interface.
213 * @return The template context factory
215 public TemplateContextFactory getTemplateContextFactory() {
216 return templateContextFactory;
219 private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
220 return getSessionManager().useSession(toadletContenxt);
223 private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
224 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
225 if (session == null) {
226 session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
231 public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
232 Collection<Sone> localSones = getCore().getLocalSones();
233 if (localSones.size() == 1) {
234 return localSones.iterator().next();
236 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
239 public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
240 Collection<Sone> localSones = getCore().getLocalSones();
241 if (localSones.size() == 1) {
242 return localSones.iterator().next();
244 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
248 * Returns the currently logged in Sone.
252 * @return The currently logged in Sone, or {@code null} if no Sone is
253 * currently logged in
255 private Sone getCurrentSone(Session session) {
256 if (session == null) {
259 String soneId = (String) session.getAttribute("Sone.CurrentSone");
260 if (soneId == null) {
263 return getCore().getLocalSone(soneId);
268 public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
269 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
273 * Sets the currently logged in Sone.
275 * @param toadletContext
276 * The toadlet context
278 * The Sone to set as currently logged in
281 public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
282 Session session = getOrCreateCurrentSession(toadletContext);
284 session.removeAttribute("Sone.CurrentSone");
286 session.setAttribute("Sone.CurrentSone", sone.getId());
291 * Returns the notification manager.
293 * @return The notification manager
295 public NotificationManager getNotifications() {
296 return notificationManager;
300 public Optional<Notification> getNotification(@Nonnull String notificationId) {
301 return Optional.fromNullable(notificationManager.getNotification(notificationId));
305 public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
306 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
309 public Translation getTranslation() {
314 * Returns the session manager of the node.
316 * @return The node’s session manager
318 public SessionManager getSessionManager() {
319 return sonePlugin.pluginRespirator().getSessionManager("Sone");
323 * Returns the node’s form password.
325 * @return The form password
327 public String getFormPassword() {
332 public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
333 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
334 .addAll(newPostNotification.getElements())
335 .addAll(localPostNotification.getElements())
337 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
341 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
342 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
343 .addAll(newReplyNotification.getElements())
344 .addAll(localReplyNotification.getElements())
346 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
354 * Starts the web interface and registers all toadlets.
356 public void start() {
361 * Stops the web interface and unregisters all toadlets.
364 pageToadletRegistry.unregisterToadlets();
372 * Register all toadlets.
374 private void registerToadlets() {
375 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
376 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
377 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
379 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
380 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
381 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
382 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
383 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
384 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
385 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
386 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
387 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
388 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
389 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
390 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
391 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
392 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
393 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
394 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
395 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
396 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
397 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
398 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
399 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
400 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
401 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
402 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
403 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
404 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
405 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
406 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
407 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
408 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
409 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
410 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
411 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
412 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
413 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
414 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
415 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
416 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
417 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
418 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
419 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
420 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
421 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
422 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
423 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
424 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
425 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
426 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
427 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
428 pageToadletRegistry.addPage(new GetImagePage(this));
429 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
430 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
431 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
432 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
433 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
434 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
435 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
436 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
437 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
438 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
439 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
440 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
441 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
442 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
443 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
444 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
445 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
446 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
447 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
448 pageToadletRegistry.addPage(new LikeAjaxPage(this));
449 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
450 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
451 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
452 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
453 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
454 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
455 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
457 pageToadletRegistry.registerToadlets();
461 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
462 pageToadletRegistry.activateDebugMode();