X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fweb%2FWebInterface.java;h=0c6322a54b1d4b857d6c1393014163afea7cf18c;hb=7a4ce0402bb7146ad791fbc52c0cf9b4d6871c91;hp=f9d6f64568e6283fab0f656043252114e4c48278;hpb=16a69fdf1e4d4b7852ca8e2abdfd09470207ec6b;p=Sone.git diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index f9d6f64..ea357d2 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -1,5 +1,5 @@ /* - * FreenetSone - WebInterface.java - Copyright © 2010 David Roden + * Sone - WebInterface.java - Copyright © 2010–2019 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,89 +17,189 @@ package net.pterodactylus.sone.web; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; +import static com.google.common.collect.FluentIterable.from; +import static java.util.logging.Logger.getLogger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Named; import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.core.ElementLoader; +import net.pterodactylus.sone.core.event.*; import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Reply; +import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.L10nFilter; +import net.pterodactylus.sone.freenet.Translation; +import net.pterodactylus.sone.main.Loaders; +import net.pterodactylus.sone.main.PluginHomepage; +import net.pterodactylus.sone.main.PluginVersion; +import net.pterodactylus.sone.main.PluginYear; import net.pterodactylus.sone.main.SonePlugin; -import net.pterodactylus.sone.template.GetPagePlugin; -import net.pterodactylus.sone.template.PostAccessor; -import net.pterodactylus.sone.template.ReplyAccessor; -import net.pterodactylus.sone.template.RequestChangeFilter; -import net.pterodactylus.sone.template.SoneAccessor; -import net.pterodactylus.sone.template.SubstringFilter; -import net.pterodactylus.sone.web.ajax.BlockSoneAjaxPage; +import net.pterodactylus.sone.notify.ListNotification; +import net.pterodactylus.sone.notify.ListNotificationFilter; +import net.pterodactylus.sone.notify.PostVisibilityFilter; +import net.pterodactylus.sone.notify.ReplyVisibilityFilter; +import net.pterodactylus.sone.template.LinkedElementRenderFilter; +import net.pterodactylus.sone.template.ParserFilter; +import net.pterodactylus.sone.template.RenderFilter; +import net.pterodactylus.sone.template.ShortenFilter; +import net.pterodactylus.sone.text.TimeTextConverter; +import net.pterodactylus.sone.web.ajax.BookmarkAjaxPage; +import net.pterodactylus.sone.web.ajax.CreatePostAjaxPage; +import net.pterodactylus.sone.web.ajax.CreateReplyAjaxPage; import net.pterodactylus.sone.web.ajax.DeletePostAjaxPage; +import net.pterodactylus.sone.web.ajax.DeleteProfileFieldAjaxPage; import net.pterodactylus.sone.web.ajax.DeleteReplyAjaxPage; +import net.pterodactylus.sone.web.ajax.DismissNotificationAjaxPage; +import net.pterodactylus.sone.web.ajax.EditAlbumAjaxPage; +import net.pterodactylus.sone.web.ajax.EditImageAjaxPage; +import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage; import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage; import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage; -import net.pterodactylus.sone.web.ajax.GetSoneStatusPage; -import net.pterodactylus.sone.web.ajax.GetTranslationPage; +import net.pterodactylus.sone.web.ajax.GetLinkedElementAjaxPage; +import net.pterodactylus.sone.web.ajax.GetNotificationsAjaxPage; +import net.pterodactylus.sone.web.ajax.GetPostAjaxPage; +import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage; +import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage; +import net.pterodactylus.sone.web.ajax.GetTimesAjaxPage; +import net.pterodactylus.sone.web.ajax.GetTranslationAjaxPage; import net.pterodactylus.sone.web.ajax.LikeAjaxPage; -import net.pterodactylus.sone.web.ajax.UnblockSoneAjaxPage; +import net.pterodactylus.sone.web.ajax.LockSoneAjaxPage; +import net.pterodactylus.sone.web.ajax.MarkAsKnownAjaxPage; +import net.pterodactylus.sone.web.ajax.MoveProfileFieldAjaxPage; +import net.pterodactylus.sone.web.ajax.UnbookmarkAjaxPage; import net.pterodactylus.sone.web.ajax.UnfollowSoneAjaxPage; import net.pterodactylus.sone.web.ajax.UnlikeAjaxPage; -import net.pterodactylus.sone.web.page.PageToadlet; -import net.pterodactylus.sone.web.page.PageToadletFactory; -import net.pterodactylus.sone.web.page.StaticPage; -import net.pterodactylus.util.logging.Logging; -import net.pterodactylus.util.service.AbstractService; -import net.pterodactylus.util.template.DateFilter; -import net.pterodactylus.util.template.DefaultTemplateFactory; -import net.pterodactylus.util.template.MatchFilter; -import net.pterodactylus.util.template.PaginationPlugin; -import net.pterodactylus.util.template.ReflectionAccessor; +import net.pterodactylus.sone.web.ajax.UnlockSoneAjaxPage; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.sone.web.page.TemplateRenderer; +import net.pterodactylus.sone.web.pages.*; +import net.pterodactylus.util.notify.Notification; +import net.pterodactylus.util.notify.NotificationManager; +import net.pterodactylus.util.notify.TemplateNotification; import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateException; -import net.pterodactylus.util.template.TemplateFactory; -import net.pterodactylus.util.template.TemplateProvider; -import net.pterodactylus.util.template.XmlFilter; +import net.pterodactylus.util.template.TemplateContextFactory; +import net.pterodactylus.util.web.RedirectPage; +import net.pterodactylus.util.web.TemplatePage; + import freenet.clients.http.SessionManager; -import freenet.clients.http.ToadletContainer; -import freenet.l10n.BaseL10n; +import freenet.clients.http.SessionManager.Session; +import freenet.clients.http.ToadletContext; + +import com.codahale.metrics.*; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; /** * Bundles functionality that a web interface of a Freenet plugin needs, e.g. * references to l10n helpers. - * - * @author David ‘Bombe’ Roden */ -public class WebInterface extends AbstractService { +public class WebInterface implements SessionProvider { /** The logger. */ - private static final Logger logger = Logging.getLogger(WebInterface.class); + private static final Logger logger = getLogger(WebInterface.class.getName()); + + /** The loaders for templates, pages, and classpath providers. */ + private final Loaders loaders; + + /** The notification manager. */ + private final NotificationManager notificationManager; /** The Sone plugin. */ private final SonePlugin sonePlugin; - /** The registered toadlets. */ - private final List pageToadlets = new ArrayList(); - /** The form password. */ private final String formPassword; - /** - * Creates a new web interface. - * - * @param sonePlugin - * The Sone plugin - */ - public WebInterface(SonePlugin sonePlugin) { - super("Sone Web Interface", false); + /** The template context factory. */ + private final TemplateContextFactory templateContextFactory; + private final TemplateRenderer templateRenderer; + + /** The parser filter. */ + private final ParserFilter parserFilter; + private final ShortenFilter shortenFilter; + private final RenderFilter renderFilter; + + private final ListNotificationFilter listNotificationFilter; + private final PostVisibilityFilter postVisibilityFilter; + private final ReplyVisibilityFilter replyVisibilityFilter; + + private final ElementLoader elementLoader; + private final LinkedElementRenderFilter linkedElementRenderFilter; + private final TimeTextConverter timeTextConverter = new TimeTextConverter(); + private final L10nFilter l10nFilter; + + private final PageToadletRegistry pageToadletRegistry; + private final MetricRegistry metricRegistry; + private final Translation translation; + + /** The “new post” notification. */ + private final ListNotification newPostNotification; + + /** The “new reply” notification. */ + private final ListNotification newReplyNotification; + + /** The invisible “local post” notification. */ + private final ListNotification localPostNotification; + + /** The invisible “local reply” notification. */ + private final ListNotification localReplyNotification; + + /** Notifications for sone inserts. */ + private final Map soneInsertNotifications = new HashMap<>(); + + @Inject + public WebInterface(SonePlugin sonePlugin, Loaders loaders, ListNotificationFilter listNotificationFilter, + PostVisibilityFilter postVisibilityFilter, ReplyVisibilityFilter replyVisibilityFilter, + ElementLoader elementLoader, TemplateContextFactory templateContextFactory, + TemplateRenderer templateRenderer, + ParserFilter parserFilter, ShortenFilter shortenFilter, + RenderFilter renderFilter, + LinkedElementRenderFilter linkedElementRenderFilter, + PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter, + NotificationManager notificationManager, @Named("newRemotePost") ListNotification newPostNotification, + @Named("localPost") ListNotification localPostNotification) { this.sonePlugin = sonePlugin; + this.loaders = loaders; + this.listNotificationFilter = listNotificationFilter; + this.postVisibilityFilter = postVisibilityFilter; + this.replyVisibilityFilter = replyVisibilityFilter; + this.elementLoader = elementLoader; + this.templateRenderer = templateRenderer; + this.parserFilter = parserFilter; + this.shortenFilter = shortenFilter; + this.renderFilter = renderFilter; + this.linkedElementRenderFilter = linkedElementRenderFilter; + this.pageToadletRegistry = pageToadletRegistry; + this.metricRegistry = metricRegistry; + this.l10nFilter = l10nFilter; + this.translation = translation; + this.notificationManager = notificationManager; + this.newPostNotification = newPostNotification; + this.localPostNotification = localPostNotification; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); + + this.templateContextFactory = templateContextFactory; + templateContextFactory.addTemplateObject("webInterface", this); + templateContextFactory.addTemplateObject("formPassword", formPassword); + + /* create notifications. */ + Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); + newReplyNotification = new ListNotification<>("new-reply-notification", "replies", newReplyNotificationTemplate, false); + + Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); + localReplyNotification = new ListNotification<>("local-reply-notification", "replies", localReplyNotificationTemplate, false); } // @@ -111,31 +211,121 @@ public class WebInterface extends AbstractService { * * @return The Sone core */ - public Core core() { + @Nonnull + public Core getCore() { return sonePlugin.core(); } /** - * Returns the l10n helper of the node. + * Returns the template context factory of the web interface. * - * @return The node’s l10n helper + * @return The template context factory */ - public BaseL10n l10n() { - return sonePlugin.l10n().getBase(); + public TemplateContextFactory getTemplateContextFactory() { + return templateContextFactory; + } + + private Session getCurrentSessionWithoutCreation(ToadletContext toadletContenxt) { + return getSessionManager().useSession(toadletContenxt); + } + + private Session getOrCreateCurrentSession(ToadletContext toadletContenxt) { + Session session = getCurrentSessionWithoutCreation(toadletContenxt); + if (session == null) { + session = getSessionManager().createSession(UUID.randomUUID().toString(), toadletContenxt); + } + return session; + } + + public Sone getCurrentSoneCreatingSession(ToadletContext toadletContext) { + Collection localSones = getCore().getLocalSones(); + if (localSones.size() == 1) { + return localSones.iterator().next(); + } + return getCurrentSone(getOrCreateCurrentSession(toadletContext)); + } + + public Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) { + Collection localSones = getCore().getLocalSones(); + if (localSones.size() == 1) { + return localSones.iterator().next(); + } + return getCurrentSone(getCurrentSessionWithoutCreation(toadletContext)); } /** - * Returns the session manager of the node. + * Returns the currently logged in Sone. * - * @return The node’s session manager + * @param session + * The session + * @return The currently logged in Sone, or {@code null} if no Sone is + * currently logged in */ - public SessionManager sessionManager() { - try { - return sonePlugin.pluginRespirator().getSessionManager(new URI("/")); - } catch (URISyntaxException use1) { - logger.log(Level.SEVERE, "Could not get Session Manager!", use1); + private Sone getCurrentSone(Session session) { + if (session == null) { + return null; + } + String soneId = (String) session.getAttribute("Sone.CurrentSone"); + if (soneId == null) { return null; } + return getCore().getLocalSone(soneId); + } + + @Override + @Nullable + public Sone getCurrentSone(@Nonnull ToadletContext toadletContext, boolean createSession) { + return createSession ? getCurrentSoneCreatingSession(toadletContext) : getCurrentSoneWithoutCreatingSession(toadletContext); + } + + /** + * Sets the currently logged in Sone. + * + * @param toadletContext + * The toadlet context + * @param sone + * The Sone to set as currently logged in + */ + @Override + public void setCurrentSone(@Nonnull ToadletContext toadletContext, @Nullable Sone sone) { + Session session = getOrCreateCurrentSession(toadletContext); + if (sone == null) { + session.removeAttribute("Sone.CurrentSone"); + } else { + session.setAttribute("Sone.CurrentSone", sone.getId()); + } + } + + /** + * Returns the notification manager. + * + * @return The notification manager + */ + public NotificationManager getNotifications() { + return notificationManager; + } + + @Nonnull + public Optional getNotification(@Nonnull String notificationId) { + return Optional.fromNullable(notificationManager.getNotification(notificationId)); + } + + @Nonnull + public Collection getNotifications(@Nullable Sone currentSone) { + return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone); + } + + public Translation getTranslation() { + return translation; + } + + /** + * Returns the session manager of the node. + * + * @return The node’s session manager + */ + public SessionManager getSessionManager() { + return sonePlugin.pluginRespirator().getSessionManager("Sone"); } /** @@ -143,28 +333,68 @@ public class WebInterface extends AbstractService { * * @return The form password */ - public String formPassword() { + public String getFormPassword() { return formPassword; } + @Nonnull + public Collection getNewPosts(@Nullable Sone currentSone) { + Set allNewPosts = ImmutableSet. builder() + .addAll(newPostNotification.getElements()) + .addAll(localPostNotification.getElements()) + .build(); + return from(allNewPosts).filter(postVisibilityFilter.isVisible(currentSone)).toSet(); + } + + /** + * Returns the replies that have been announced as new in the + * {@link #newReplyNotification}. + * + * @return The new replies + */ + public Set getNewReplies() { + return ImmutableSet. builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build(); + } + + @Nonnull + public Collection getNewReplies(@Nullable Sone currentSone) { + Set allNewReplies = ImmutableSet.builder() + .addAll(newReplyNotification.getElements()) + .addAll(localReplyNotification.getElements()) + .build(); + return from(allNewReplies).filter(replyVisibilityFilter.isVisible(currentSone)).toSet(); + } + // - // SERVICE METHODS + // PRIVATE ACCESSORS // /** - * {@inheritDoc} + * Returns whether the first start notification is currently displayed. + * + * @return {@code true} if the first-start notification is currently + * displayed, {@code false} otherwise */ - @Override - protected void serviceStart() { + private boolean hasFirstStartNotification() { + return notificationManager.getNotification("first-start-notification") != null; + } + + // + // ACTIONS + // + + /** + * Starts the web interface and registers all toadlets. + */ + public void start() { registerToadlets(); } /** - * {@inheritDoc} + * Stops the web interface and unregisters all toadlets. */ - @Override - protected void serviceStop() { - unregisterToadlets(); + public void stop() { + pageToadletRegistry.unregisterToadlets(); } // @@ -175,165 +405,215 @@ public class WebInterface extends AbstractService { * Register all toadlets. */ private void registerToadlets() { - DefaultTemplateFactory templateFactory = new DefaultTemplateFactory(); - templateFactory.addAccessor(Object.class, new ReflectionAccessor()); - templateFactory.addAccessor(Sone.class, new SoneAccessor(core())); - templateFactory.addAccessor(Post.class, new PostAccessor(core())); - templateFactory.addAccessor(Reply.class, new ReplyAccessor(core())); - templateFactory.addFilter("date", new DateFilter()); - templateFactory.addFilter("l10n", new L10nFilter(l10n())); - templateFactory.addFilter("substring", new SubstringFilter()); - templateFactory.addFilter("xml", new XmlFilter()); - templateFactory.addFilter("change", new RequestChangeFilter()); - templateFactory.addFilter("match", new MatchFilter()); - templateFactory.addPlugin("getpage", new GetPagePlugin()); - templateFactory.addPlugin("paginate", new PaginationPlugin()); - templateFactory.setTemplateProvider(new ClassPathTemplateProvider(templateFactory)); - templateFactory.addTemplateObject("formPassword", formPassword); - - Template loginTemplate = templateFactory.createTemplate(createReader("/templates/login.html")); - Template indexTemplate = templateFactory.createTemplate(createReader("/templates/index.html")); - Template addSoneTemplate = templateFactory.createTemplate(createReader("/templates/addSone.html")); - Template loadSoneTemplate = templateFactory.createTemplate(createReader("/templates/loadSone.html")); - Template knownSonesTemplate = templateFactory.createTemplate(createReader("/templates/knownSones.html")); - Template createSoneTemplate = templateFactory.createTemplate(createReader("/templates/createSone.html")); - Template createPostTemplate = templateFactory.createTemplate(createReader("/templates/createPost.html")); - Template createReplyTemplate = templateFactory.createTemplate(createReader("/templates/createReply.html")); - Template editProfileTemplate = templateFactory.createTemplate(createReader("/templates/editProfile.html")); - Template backupProfileTemplate = templateFactory.createTemplate(createReader("/templates/backup.xml")); - Template viewSoneTemplate = templateFactory.createTemplate(createReader("/templates/viewSone.html")); - Template blockSoneTemplate = templateFactory.createTemplate(createReader("/templates/blockSone.html")); - Template unblockSoneTemplate = templateFactory.createTemplate(createReader("/templates/unblockSone.html")); - Template viewPostTemplate = templateFactory.createTemplate(createReader("/templates/viewPost.html")); - Template likePostTemplate = templateFactory.createTemplate(createReader("/templates/like.html")); - Template unlikePostTemplate = templateFactory.createTemplate(createReader("/templates/unlike.html")); - Template deletePostTemplate = templateFactory.createTemplate(createReader("/templates/deletePost.html")); - Template deleteReplyTemplate = templateFactory.createTemplate(createReader("/templates/deleteReply.html")); - Template followSoneTemplate = templateFactory.createTemplate(createReader("/templates/followSone.html")); - Template unfollowSoneTemplate = templateFactory.createTemplate(createReader("/templates/unfollowSone.html")); - Template deleteSoneTemplate = templateFactory.createTemplate(createReader("/templates/deleteSone.html")); - Template noPermissionTemplate = templateFactory.createTemplate(createReader("/templates/noPermission.html")); - Template logoutTemplate = templateFactory.createTemplate(createReader("/templates/logout.html")); - - PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/"); - pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LoadSonePage(loadSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new AddSonePage(addSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new BackupProfilePage(backupProfileTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostPage(createPostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyPage(createReplyTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewSonePage(viewSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new BlockSonePage(blockSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnblockSonePage(unblockSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new ViewPostPage(viewPostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LikePage(likePostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikePage(unlikePostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostPage(deletePostTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyPage(deleteReplyTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSonePage(followSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSonePage(unfollowSoneTemplate, this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteSonePage(deleteSoneTemplate, this), "DeleteSone")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LoginPage(loginTemplate, this), "Login")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LogoutPage(logoutTemplate, this), "Logout")); - pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("noPermission.html", noPermissionTemplate, "Page.NoPermission.Title", this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("css/", "/static/css/", "text/css"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("javascript/", "/static/javascript/", "text/javascript"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new StaticPage("images/", "/static/images/", "image/png"))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new GetSoneStatusPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DeletePostAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new DeleteReplyAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new BlockSoneAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnblockSoneAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new LikeAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlikeAjaxPage(this))); - pageToadlets.add(pageToadletFactory.createPageToadlet(new GetLikesAjaxPage(this))); - - ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer(); - toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Name", "Navigation.Menu.Tooltip", sonePlugin); - for (PageToadlet toadlet : pageToadlets) { - String menuName = toadlet.getMenuName(); - if (menuName != null) { - toadletContainer.register(toadlet, "Navigation.Menu.Name", toadlet.path(), true, "Navigation.Menu.Item." + menuName + ".Name", "Navigation.Menu.Item." + menuName + ".Tooltip", false, toadlet); - } else { - toadletContainer.register(toadlet, null, toadlet.path(), true, false); + Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html"); + Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html"); + Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml"); + + pageToadletRegistry.addPage(new RedirectPage("", "index.html")); + pageToadletRegistry.addPage(new IndexPage(this, loaders, templateRenderer, postVisibilityFilter)); + pageToadletRegistry.addPage(new NewPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new CreateSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new KnownSonesPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EditProfilePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EditProfileFieldPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeleteProfileFieldPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new CreatePostPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new CreateReplyPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new ViewSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new ViewPostPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new LikePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new UnlikePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeletePostPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeleteReplyPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new LockSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new UnlockSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new FollowSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new UnfollowSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new ImageBrowserPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new CreateAlbumPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EditAlbumPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeleteAlbumPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new UploadImagePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EditImagePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeleteImagePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new MarkAsKnownPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new BookmarkPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new UnbookmarkPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new BookmarksPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new SearchPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DeleteSonePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new LoginPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new LogoutPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new OptionsPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new RescuePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new AboutPage(this, loaders, templateRenderer, new PluginVersion(SonePlugin.getPluginVersion()), new PluginYear(sonePlugin.getYear()), new PluginHomepage(sonePlugin.getHomepage()))); + pageToadletRegistry.addPage(new InvalidPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new NoPermissionPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EmptyImageTitlePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new EmptyAlbumTitlePage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer)); + pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer)); + pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry)); + pageToadletRegistry.addPage(loaders.loadStaticPage("css/", "/static/css/", "text/css")); + pageToadletRegistry.addPage(loaders.loadStaticPage("javascript/", "/static/javascript/", "text/javascript")); + pageToadletRegistry.addPage(loaders.loadStaticPage("images/", "/static/images/", "image/png")); + pageToadletRegistry.addPage(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate)); + pageToadletRegistry.addPage(new GetImagePage(this)); + pageToadletRegistry.addPage(new GetTranslationAjaxPage(this)); + pageToadletRegistry.addPage(new GetStatusAjaxPage(this, elementLoader, timeTextConverter, l10nFilter, TimeZone.getDefault())); + pageToadletRegistry.addPage(new GetNotificationsAjaxPage(this)); + pageToadletRegistry.addPage(new DismissNotificationAjaxPage(this)); + pageToadletRegistry.addPage(new CreatePostAjaxPage(this)); + pageToadletRegistry.addPage(new CreateReplyAjaxPage(this)); + pageToadletRegistry.addPage(new GetReplyAjaxPage(this, replyTemplate)); + pageToadletRegistry.addPage(new GetPostAjaxPage(this, postTemplate)); + pageToadletRegistry.addPage(new GetLinkedElementAjaxPage(this, elementLoader, linkedElementRenderFilter)); + pageToadletRegistry.addPage(new GetTimesAjaxPage(this, timeTextConverter, l10nFilter, TimeZone.getDefault())); + pageToadletRegistry.addPage(new MarkAsKnownAjaxPage(this)); + pageToadletRegistry.addPage(new DeletePostAjaxPage(this)); + pageToadletRegistry.addPage(new DeleteReplyAjaxPage(this)); + pageToadletRegistry.addPage(new LockSoneAjaxPage(this)); + pageToadletRegistry.addPage(new UnlockSoneAjaxPage(this)); + pageToadletRegistry.addPage(new FollowSoneAjaxPage(this)); + pageToadletRegistry.addPage(new UnfollowSoneAjaxPage(this)); + pageToadletRegistry.addPage(new EditAlbumAjaxPage(this)); + pageToadletRegistry.addPage(new EditImageAjaxPage(this, parserFilter, shortenFilter, renderFilter)); + pageToadletRegistry.addPage(new LikeAjaxPage(this)); + pageToadletRegistry.addPage(new UnlikeAjaxPage(this)); + pageToadletRegistry.addPage(new GetLikesAjaxPage(this)); + pageToadletRegistry.addPage(new BookmarkAjaxPage(this)); + pageToadletRegistry.addPage(new UnbookmarkAjaxPage(this)); + pageToadletRegistry.addPage(new EditProfileFieldAjaxPage(this)); + pageToadletRegistry.addPage(new DeleteProfileFieldAjaxPage(this)); + pageToadletRegistry.addPage(new MoveProfileFieldAjaxPage(this)); + + pageToadletRegistry.registerToadlets(); + } + + /** + * Returns the Sone insert notification for the given Sone. If no + * notification for the given Sone exists, a new notification is created and + * cached. + * + * @param sone + * The Sone to get the insert notification for + * @return The Sone insert notification + */ + private TemplateNotification getSoneInsertNotification(Sone sone) { + synchronized (soneInsertNotifications) { + TemplateNotification templateNotification = soneInsertNotifications.get(sone); + if (templateNotification == null) { + templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html")); + templateNotification.set("insertSone", sone); + soneInsertNotifications.put(sone, templateNotification); } + return templateNotification; } } + // + // EVENT HANDLERS + // + /** - * Unregisters all toadlets. + * Notifies the web interface that a new {@link PostReply} was found. + * + * @param newPostReplyFoundEvent + * The event */ - private void unregisterToadlets() { - ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer(); - for (PageToadlet pageToadlet : pageToadlets) { - toadletContainer.unregister(pageToadlet); + @Subscribe + public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) { + PostReply reply = newPostReplyFoundEvent.getPostReply(); + boolean isLocal = reply.getSone().isLocal(); + if (isLocal) { + localReplyNotification.add(reply); + } else { + newReplyNotification.add(reply); + } + if (!hasFirstStartNotification()) { + notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification); } - toadletContainer.getPageMaker().removeNavigationCategory("Navigation.Menu.Name"); + } + + @Subscribe + public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) { + removePost(markPostKnownEvent.getPost()); + } + + @Subscribe + public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) { + removeReply(markPostReplyKnownEvent.getPostReply()); + } + + @Subscribe + public void postRemoved(PostRemovedEvent postRemovedEvent) { + removePost(postRemovedEvent.getPost()); + } + + private void removePost(Post post) { + newPostNotification.remove(post); + } + + @Subscribe + public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) { + removeReply(postReplyRemovedEvent.getPostReply()); + } + + private void removeReply(PostReply reply) { + newReplyNotification.remove(reply); + localReplyNotification.remove(reply); } /** - * Creates a {@link Reader} from the {@link InputStream} for the resource - * with the given name. + * Notifies the web interface that a {@link Sone} is being inserted. * - * @param resourceName - * The name of the resource - * @return A {@link Reader} for the resource + * @param soneInsertingEvent + * The event */ - private Reader createReader(String resourceName) { - try { - return new InputStreamReader(getClass().getResourceAsStream(resourceName), "UTF-8"); - } catch (UnsupportedEncodingException uee1) { - return null; + @Subscribe + public void soneInserting(SoneInsertingEvent soneInsertingEvent) { + TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.getSone()); + soneInsertNotification.set("soneStatus", "inserting"); + if (soneInsertingEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { + notificationManager.addNotification(soneInsertNotification); } } /** - * Template provider implementation that uses - * {@link WebInterface#createReader(String)} to load templates for - * inclusion. + * Notifies the web interface that a {@link Sone} was inserted. * - * @author David ‘Bombe’ Roden + * @param soneInsertedEvent + * The event */ - private class ClassPathTemplateProvider implements TemplateProvider { - - /** The template factory. */ - private final TemplateFactory templateFactory; - - /** - * Creates a new template provider that locates templates on the - * classpath. - * - * @param templateFactory - * The template factory to create the templates - */ - public ClassPathTemplateProvider(TemplateFactory templateFactory) { - this.templateFactory = templateFactory; + @Subscribe + public void soneInserted(SoneInsertedEvent soneInsertedEvent) { + TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.getSone()); + soneInsertNotification.set("soneStatus", "inserted"); + soneInsertNotification.set("insertDuration", soneInsertedEvent.getInsertDuration() / 1000); + if (soneInsertedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { + notificationManager.addNotification(soneInsertNotification); } + } - /** - * {@inheritDoc} - */ - @Override - @SuppressWarnings("synthetic-access") - public Template getTemplate(String templateName) { - Reader templateReader = createReader("/templates/" + templateName); - if (templateReader == null) { - return null; - } - Template template = templateFactory.createTemplate(templateReader); - try { - template.parse(); - } catch (TemplateException te1) { - logger.log(Level.WARNING, "Could not parse template “" + templateName + "” for inclusion!", te1); - } - return template; + /** + * Notifies the web interface that a {@link Sone} insert was aborted. + * + * @param soneInsertAbortedEvent + * The event + */ + @Subscribe + public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) { + TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.getSone()); + soneInsertNotification.set("soneStatus", "insert-aborted"); + soneInsertNotification.set("insert-error", soneInsertAbortedEvent.getCause()); + if (soneInsertAbortedEvent.getSone().getOptions().isSoneInsertNotificationEnabled()) { + notificationManager.addNotification(soneInsertNotification); } + } + @Subscribe + public void debugActivated(@Nonnull DebugActivatedEvent debugActivatedEvent) { + pageToadletRegistry.activateDebugMode(); } + }