2a322412dd35c0e58e0e678c07a380d8ac5b5de6
[Sone.git] / src / main / java / net / pterodactylus / sone / web / WebInterface.java
1 /*
2  * Sone - WebInterface.java - Copyright © 2010–2019 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.sone.web;
19
20 import static com.google.common.collect.FluentIterable.from;
21 import static java.util.logging.Logger.getLogger;
22
23 import java.util.Collection;
24 import java.util.Set;
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;
31
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;
90
91 import freenet.clients.http.SessionManager;
92 import freenet.clients.http.SessionManager.Session;
93 import freenet.clients.http.ToadletContext;
94
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;
100
101 /**
102  * Bundles functionality that a web interface of a Freenet plugin needs, e.g.
103  * references to l10n helpers.
104  */
105 public class WebInterface implements SessionProvider {
106
107         /** The logger. */
108         private static final Logger logger = getLogger(WebInterface.class.getName());
109
110         /** The loaders for templates, pages, and classpath providers. */
111         private final Loaders loaders;
112
113         /** The notification manager. */
114         private final NotificationManager notificationManager;
115
116         /** The Sone plugin. */
117         private final SonePlugin sonePlugin;
118
119         /** The form password. */
120         private final String formPassword;
121
122         /** The template context factory. */
123         private final TemplateContextFactory templateContextFactory;
124         private final TemplateRenderer templateRenderer;
125
126         /** The parser filter. */
127         private final ParserFilter parserFilter;
128         private final ShortenFilter shortenFilter;
129         private final RenderFilter renderFilter;
130
131         private final ListNotificationFilter listNotificationFilter;
132         private final PostVisibilityFilter postVisibilityFilter;
133         private final ReplyVisibilityFilter replyVisibilityFilter;
134
135         private final ElementLoader elementLoader;
136         private final LinkedElementRenderFilter linkedElementRenderFilter;
137         private final TimeTextConverter timeTextConverter = new TimeTextConverter();
138         private final L10nFilter l10nFilter;
139
140         private final PageToadletRegistry pageToadletRegistry;
141         private final MetricRegistry metricRegistry;
142         private final Translation translation;
143
144         /** The “new post” notification. */
145         private final ListNotification<Post> newPostNotification;
146
147         /** The “new reply” notification. */
148         private final ListNotification<PostReply> newReplyNotification;
149
150         /** The invisible “local post” notification. */
151         private final ListNotification<Post> localPostNotification;
152
153         /** The invisible “local reply” notification. */
154         private final ListNotification<PostReply> localReplyNotification;
155
156         @Inject
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();
190
191                 this.templateContextFactory = templateContextFactory;
192                 templateContextFactory.addTemplateObject("webInterface", this);
193                 templateContextFactory.addTemplateObject("formPassword", formPassword);
194         }
195
196         //
197         // ACCESSORS
198         //
199
200         /**
201          * Returns the Sone core used by the Sone plugin.
202          *
203          * @return The Sone core
204          */
205         @Nonnull
206         public Core getCore() {
207                 return sonePlugin.core();
208         }
209
210         /**
211          * Returns the template context factory of the web interface.
212          *
213          * @return The template context factory
214          */
215         public TemplateContextFactory getTemplateContextFactory() {
216                 return templateContextFactory;
217         }
218
219         private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) {
220                 return getSessionManager().useSession(toadletContenxt);
221         }
222
223         private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) {
224                 Session session = getCurrentSessionWithoutCreation(toadletContenxt);
225                 if (session == null) {
226                         session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt);
227                 }
228                 return session;
229         }
230
231         public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) {
232                 Collection<Sone> localSones = getCore().getLocalSones();
233                 if (localSones.size() == 1) {
234                         return localSones.iterator().next();
235                 }
236                 return getCurrentSone(getOrCreateCurrentSession(toadletContext));
237         }
238
239         public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) {
240                 Collection<Sone> localSones = getCore().getLocalSones();
241                 if (localSones.size() == 1) {
242                         return localSones.iterator().next();
243                 }
244                 return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext));
245         }
246
247         /**
248          * Returns the currently logged in Sone.
249          *
250          * @param session
251          *            The session
252          * @return The currently logged in Sone, or {@code null} if no Sone is
253          *         currently logged in
254          */
255         private Sone getCurrentSone(Session session) {
256                 if (session == null) {
257                         return null;
258                 }
259                 String soneId = (String) session.getAttribute("Sone.CurrentSone");
260                 if (soneId == null) {
261                         return null;
262                 }
263                 return getCore().getLocalSone(soneId);
264         }
265
266         @Override
267         @Nullable
268         public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) {
269                 return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext);
270         }
271
272         /**
273          * Sets the currently logged in Sone.
274          *
275          * @param toadletContext
276          *            The toadlet context
277          * @param sone
278          *            The Sone to set as currently logged in
279          */
280         @Override
281         public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) {
282                 Session session = getOrCreateCurrentSession(toadletContext);
283                 if (sone == null) {
284                         session.removeAttribute("Sone.CurrentSone");
285                 } else {
286                         session.setAttribute("Sone.CurrentSone", sone.getId());
287                 }
288         }
289
290         /**
291          * Returns the notification manager.
292          *
293          * @return The notification manager
294          */
295         public NotificationManager getNotifications() {
296                 return notificationManager;
297         }
298
299         @Nonnull
300         public Optional<Notification> getNotification(@Nonnull String notificationId) {
301                 return Optional.fromNullable(notificationManager.getNotification(notificationId));
302         }
303
304         @Nonnull
305         public Collection<Notification> getNotifications(@Nullable Sone currentSone) {
306                 return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone);
307         }
308
309         public Translation getTranslation() {
310                 return translation;
311         }
312
313         /**
314          * Returns the session manager of the node.
315          *
316          * @return The node’s session manager
317          */
318         public SessionManager getSessionManager() {
319                 return sonePlugin.pluginRespirator().getSessionManager("Sone");
320         }
321
322         /**
323          * Returns the node’s form password.
324          *
325          * @return The form password
326          */
327         public String getFormPassword() {
328                 return formPassword;
329         }
330
331         @Nonnull
332         public Collection<Post> getNewPosts(@Nullable Sone currentSone) {
333                 Set<Post> allNewPosts = ImmutableSet.<Post> builder()
334                                 .addAll(newPostNotification.getElements())
335                                 .addAll(localPostNotification.getElements())
336                                 .build();
337                 return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet();
338         }
339
340         /**
341          * Returns the replies that have been announced as new in the
342          * {@link #newReplyNotification}.
343          *
344          * @return The new replies
345          */
346         public Set<PostReply> getNewReplies() {
347                 return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
348         }
349
350         @Nonnull
351         public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
352                 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
353                                 .addAll(newReplyNotification.getElements())
354                                 .addAll(localReplyNotification.getElements())
355                                 .build();
356                 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
357         }
358
359         //
360         // PRIVATE ACCESSORS
361         //
362
363         /**
364          * Returns whether the first start notification is currently displayed.
365          *
366          * @return {@code true} if the first-start notification is currently
367          *         displayed, {@code false} otherwise
368          */
369         private boolean hasFirstStartNotification() {
370                 return notificationManager.getNotification("first-start-notification") != null;
371         }
372
373         //
374         // ACTIONS
375         //
376
377         /**
378          * Starts the web interface and registers all toadlets.
379          */
380         public void start() {
381                 registerToadlets();
382         }
383
384         /**
385          * Stops the web interface and unregisters all toadlets.
386          */
387         public void stop() {
388                 pageToadletRegistry.unregisterToadlets();
389         }
390
391         //
392         // PRIVATE METHODS
393         //
394
395         /**
396          * Register all toadlets.
397          */
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");
402
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));
480
481                 pageToadletRegistry.registerToadlets();
482         }
483
484         @Subscribe
485         public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
486                 pageToadletRegistry.activateDebugMode();
487         }
488
489 }