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;
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 * Returns the replies that have been announced as new in the
342 * {@link #newReplyNotification}.
344 * @return The new replies
346 public Set<PostReply> getNewReplies() {
347 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
351 public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
352 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
353 .addAll(newReplyNotification.getElements())
354 .addAll(localReplyNotification.getElements())
356 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
364 * Returns whether the first start notification is currently displayed.
366 * @return {@code true} if the first-start notification is currently
367 * displayed, {@code false} otherwise
369 private boolean hasFirstStartNotification() {
370 return notificationManager.getNotification("first-start-notification") != null;
378 * Starts the web interface and registers all toadlets.
380 public void start() {
385 * Stops the web interface and unregisters all toadlets.
388 pageToadletRegistry.unregisterToadlets();
396 * Register all toadlets.
398 private void registerToadlets() {
399 Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html");
400 Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html");
401 Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml");
403 pageToadletRegistry.addPage(new RedirectPage<FreenetRequest>("", "index.html"));
404 pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter));
405 pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer));
406 pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer));
407 pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer));
408 pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer));
409 pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer));
410 pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer));
411 pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer));
412 pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer));
413 pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer));
414 pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer));
415 pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer));
416 pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer));
417 pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer));
418 pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer));
419 pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer));
420 pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer));
421 pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer));
422 pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer));
423 pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer));
424 pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer));
425 pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer));
426 pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer));
427 pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer));
428 pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer));
429 pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer));
430 pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer));
431 pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer));
432 pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer));
433 pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer));
434 pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer));
435 pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer));
436 pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer));
437 pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer));
438 pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer));
439 pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer));
440 pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage())));
441 pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer));
442 pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer));
443 pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer));
444 pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer));
445 pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer));
446 pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer));
447 pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry));
448 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("css/", "/static/css/", "text/css"));
449 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("javascript/", "/static/javascript/", "text/javascript"));
450 pageToadletRegistry.addPage(loaders.<FreenetRequest>loadStaticPage("images/", "/static/images/", "image/png"));
451 pageToadletRegistry.addPage(new TemplatePage<FreenetRequest>("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate));
452 pageToadletRegistry.addPage(new GetImagePage(this));
453 pageToadletRegistry.addPage(new GetTranslationAjaxPage(this));
454 pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault()));
455 pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this));
456 pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this));
457 pageToadletRegistry.addPage(new CreatePostAjaxPage(this));
458 pageToadletRegistry.addPage(new CreateReplyAjaxPage(this));
459 pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate));
460 pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate));
461 pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter));
462 pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault()));
463 pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this));
464 pageToadletRegistry.addPage(new DeletePostAjaxPage(this));
465 pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this));
466 pageToadletRegistry.addPage(new LockSoneAjaxPage(this));
467 pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this));
468 pageToadletRegistry.addPage(new FollowSoneAjaxPage(this));
469 pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this));
470 pageToadletRegistry.addPage(new EditAlbumAjaxPage(this));
471 pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter));
472 pageToadletRegistry.addPage(new LikeAjaxPage(this));
473 pageToadletRegistry.addPage(new UnlikeAjaxPage(this));
474 pageToadletRegistry.addPage(new GetLikesAjaxPage(this));
475 pageToadletRegistry.addPage(new BookmarkAjaxPage(this));
476 pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this));
477 pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this));
478 pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this));
479 pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this));
481 pageToadletRegistry.registerToadlets();
485 public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
486 pageToadletRegistry.activateDebugMode();