edc3bab4a25c6a1878258d1987eef7d0b974a29e
[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         @Nonnull
341         public Collection<PostReply> getNewReplies(@Nullable Sone currentSone) {
342                 Set<PostReply> allNewReplies = ImmutableSet.<PostReply>builder()
343                                 .addAll(newReplyNotification.getElements())
344                                 .addAll(localReplyNotification.getElements())
345                                 .build();
346                 return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet();
347         }
348
349         //
350         // ACTIONS
351         //
352
353         /**
354          * Starts the web interface and registers all toadlets.
355          */
356         public void start() {
357                 registerToadlets();
358         }
359
360         /**
361          * Stops the web interface and unregisters all toadlets.
362          */
363         public void stop() {
364                 pageToadletRegistry.unregisterToadlets();
365         }
366
367         //
368         // PRIVATE METHODS
369         //
370
371         /**
372          * Register all toadlets.
373          */
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");
378
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));
456
457                 pageToadletRegistry.registerToadlets();
458         }
459
460         @Subscribe
461         public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) {
462                 pageToadletRegistry.activateDebugMode();
463         }
464
465 }