Use classpath template provider from utils.
[Sone.git] / src / main / java / net / pterodactylus / sone / web / WebInterface.java
index 9dd9954..00f8cf9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebInterface.java - Copyright © 2010 David Roden
+ * Sone - WebInterface.java - Copyright © 2010–2012 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
@@ -40,6 +40,8 @@ import net.pterodactylus.sone.core.CoreListener;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
@@ -52,10 +54,12 @@ import net.pterodactylus.sone.template.CollectionAccessor;
 import net.pterodactylus.sone.template.CssClassNameFilter;
 import net.pterodactylus.sone.template.HttpRequestAccessor;
 import net.pterodactylus.sone.template.IdentityAccessor;
+import net.pterodactylus.sone.template.ImageAccessor;
 import net.pterodactylus.sone.template.ImageLinkFilter;
 import net.pterodactylus.sone.template.JavascriptFilter;
 import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.template.PostAccessor;
+import net.pterodactylus.sone.template.ProfileAccessor;
 import net.pterodactylus.sone.template.ReplyAccessor;
 import net.pterodactylus.sone.template.ReplyGroupFilter;
 import net.pterodactylus.sone.template.RequestChangeFilter;
@@ -75,11 +79,12 @@ 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.DistrustAjaxPage;
+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.GetNotificationAjaxPage;
+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;
@@ -98,18 +103,13 @@ import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.sone.web.page.PageToadlet;
 import net.pterodactylus.sone.web.page.PageToadletFactory;
-import net.pterodactylus.util.cache.Cache;
-import net.pterodactylus.util.cache.CacheException;
-import net.pterodactylus.util.cache.CacheItem;
-import net.pterodactylus.util.cache.DefaultCacheItem;
-import net.pterodactylus.util.cache.MemoryCache;
-import net.pterodactylus.util.cache.ValueRetriever;
 import net.pterodactylus.util.collection.SetBuilder;
-import net.pterodactylus.util.filter.Filters;
+import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
 import net.pterodactylus.util.notify.TemplateNotification;
+import net.pterodactylus.util.template.ClassPathTemplateProvider;
 import net.pterodactylus.util.template.CollectionSortFilter;
 import net.pterodactylus.util.template.ContainsFilter;
 import net.pterodactylus.util.template.DateFilter;
@@ -117,15 +117,14 @@ import net.pterodactylus.util.template.FormatFilter;
 import net.pterodactylus.util.template.HtmlFilter;
 import net.pterodactylus.util.template.MatchFilter;
 import net.pterodactylus.util.template.ModFilter;
-import net.pterodactylus.util.template.Provider;
+import net.pterodactylus.util.template.PaginationFilter;
 import net.pterodactylus.util.template.ReflectionAccessor;
 import net.pterodactylus.util.template.ReplaceFilter;
 import net.pterodactylus.util.template.StoreFilter;
 import net.pterodactylus.util.template.Template;
-import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateContextFactory;
-import net.pterodactylus.util.template.TemplateException;
 import net.pterodactylus.util.template.TemplateParser;
+import net.pterodactylus.util.template.TemplateProvider;
 import net.pterodactylus.util.template.XmlFilter;
 import net.pterodactylus.util.thread.Ticker;
 import net.pterodactylus.util.version.Version;
@@ -133,9 +132,9 @@ import net.pterodactylus.util.web.RedirectPage;
 import net.pterodactylus.util.web.StaticPage;
 import net.pterodactylus.util.web.TemplatePage;
 import freenet.clients.http.SessionManager;
+import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContainer;
 import freenet.clients.http.ToadletContext;
-import freenet.clients.http.SessionManager.Session;
 import freenet.l10n.BaseL10n;
 import freenet.support.api.HTTPRequest;
 
@@ -168,6 +167,9 @@ public class WebInterface implements CoreListener {
        /** The Sone text parser. */
        private final SoneTextParser soneTextParser;
 
+       /** The parser filter. */
+       private final ParserFilter parserFilter;
+
        /** The “new Sone” notification. */
        private final ListNotification<Sone> newSoneNotification;
 
@@ -175,13 +177,13 @@ public class WebInterface implements CoreListener {
        private final ListNotification<Post> newPostNotification;
 
        /** The “new reply” notification. */
-       private final ListNotification<Reply> newReplyNotification;
+       private final ListNotification<PostReply> newReplyNotification;
 
        /** The invisible “local post” notification. */
        private final ListNotification<Post> localPostNotification;
 
        /** The invisible “local reply” notification. */
-       private final ListNotification<Reply> localReplyNotification;
+       private final ListNotification<PostReply> localReplyNotification;
 
        /** The “you have been mentioned” notification. */
        private final ListNotification<Post> mentionNotification;
@@ -213,7 +215,6 @@ public class WebInterface implements CoreListener {
         * @param sonePlugin
         *            The Sone plugin
         */
-       @SuppressWarnings("synthetic-access")
        public WebInterface(SonePlugin sonePlugin) {
                this.sonePlugin = sonePlugin;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
@@ -226,31 +227,34 @@ public class WebInterface implements CoreListener {
                templateContextFactory.addAccessor(Post.class, new PostAccessor(getCore()));
                templateContextFactory.addAccessor(Reply.class, new ReplyAccessor(getCore()));
                templateContextFactory.addAccessor(Album.class, new AlbumAccessor());
+               templateContextFactory.addAccessor(Image.class, new ImageAccessor());
                templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
                templateContextFactory.addAccessor(Trust.class, new TrustAccessor());
                templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor());
+               templateContextFactory.addAccessor(Profile.class, new ProfileAccessor(getCore()));
                templateContextFactory.addFilter("date", new DateFilter());
                templateContextFactory.addFilter("html", new HtmlFilter());
                templateContextFactory.addFilter("replace", new ReplaceFilter());
                templateContextFactory.addFilter("store", new StoreFilter());
-               templateContextFactory.addFilter("l10n", new L10nFilter(getL10n()));
+               templateContextFactory.addFilter("l10n", new L10nFilter(this));
                templateContextFactory.addFilter("substring", new SubstringFilter());
                templateContextFactory.addFilter("xml", new XmlFilter());
                templateContextFactory.addFilter("change", new RequestChangeFilter());
                templateContextFactory.addFilter("match", new MatchFilter());
                templateContextFactory.addFilter("css", new CssClassNameFilter());
                templateContextFactory.addFilter("js", new JavascriptFilter());
-               templateContextFactory.addFilter("parse", new ParserFilter(getCore(), templateContextFactory, soneTextParser));
+               templateContextFactory.addFilter("parse", parserFilter = new ParserFilter(getCore(), templateContextFactory, soneTextParser));
                templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
                templateContextFactory.addFilter("format", new FormatFilter());
                templateContextFactory.addFilter("sort", new CollectionSortFilter());
-               templateContextFactory.addFilter("image-link", new ImageLinkFilter(templateContextFactory));
+               templateContextFactory.addFilter("image-link", new ImageLinkFilter(getCore(), templateContextFactory));
                templateContextFactory.addFilter("replyGroup", new ReplyGroupFilter());
                templateContextFactory.addFilter("in", new ContainsFilter());
                templateContextFactory.addFilter("unique", new UniqueElementFilter());
                templateContextFactory.addFilter("mod", new ModFilter());
-               templateContextFactory.addProvider(Provider.TEMPLATE_CONTEXT_PROVIDER);
-               templateContextFactory.addProvider(new ClassPathTemplateProvider());
+               templateContextFactory.addFilter("paginate", new PaginationFilter());
+               templateContextFactory.addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER);
+               templateContextFactory.addProvider(new ClassPathTemplateProvider(WebInterface.class, "/templates/"));
                templateContextFactory.addTemplateObject("webInterface", this);
                templateContextFactory.addTemplateObject("formPassword", formPassword);
 
@@ -265,10 +269,10 @@ public class WebInterface implements CoreListener {
                localPostNotification = new ListNotification<Post>("local-post-notification", "posts", localPostNotificationTemplate, false);
 
                Template newReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
-               newReplyNotification = new ListNotification<Reply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
+               newReplyNotification = new ListNotification<PostReply>("new-reply-notification", "replies", newReplyNotificationTemplate, false);
 
                Template localReplyNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/newReplyNotification.html"));
-               localReplyNotification = new ListNotification<Reply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
+               localReplyNotification = new ListNotification<PostReply>("local-reply-notification", "replies", localReplyNotificationTemplate, false);
 
                Template mentionNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/mentionNotification.html"));
                mentionNotification = new ListNotification<Post>("mention-notification", "posts", mentionNotificationTemplate, false);
@@ -463,8 +467,8 @@ public class WebInterface implements CoreListener {
         *
         * @return The new replies
         */
-       public Set<Reply> getNewReplies() {
-               return new SetBuilder<Reply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
+       public Set<PostReply> getNewReplies() {
+               return new SetBuilder<PostReply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
        }
 
        /**
@@ -573,6 +577,7 @@ public class WebInterface implements CoreListener {
                Template emptyTemplate = TemplateParser.parse(new StringReader(""));
                Template loginTemplate = TemplateParser.parse(createReader("/templates/login.html"));
                Template indexTemplate = TemplateParser.parse(createReader("/templates/index.html"));
+               Template newTemplate = TemplateParser.parse(createReader("/templates/new.html"));
                Template knownSonesTemplate = TemplateParser.parse(createReader("/templates/knownSones.html"));
                Template createSoneTemplate = TemplateParser.parse(createReader("/templates/createSone.html"));
                Template createPostTemplate = TemplateParser.parse(createReader("/templates/createPost.html"));
@@ -603,6 +608,7 @@ public class WebInterface implements CoreListener {
                PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/");
                pageToadlets.add(pageToadletFactory.createPageToadlet(new RedirectPage<FreenetRequest>("", "index.html")));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index"));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new NewPage(newTemplate, this), "New"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateSonePage(createSoneTemplate, this), "CreateSone"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new KnownSonesPage(knownSonesTemplate, this), "KnownSones"));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new EditProfilePage(editProfileTemplate, this), "EditProfile"));
@@ -651,7 +657,7 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetImagePage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationAjaxPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationsAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this)));
@@ -665,7 +671,8 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UnlockSoneAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new FollowSoneAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UnfollowSoneAjaxPage(this)));
-               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditAlbumAjaxPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new EditImageAjaxPage(this, parserFilter)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new TrustAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DistrustAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new UntrustAjaxPage(this)));
@@ -679,11 +686,11 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new MoveProfileFieldAjaxPage(this)));
 
                ToadletContainer toadletContainer = sonePlugin.pluginRespirator().getToadletContainer();
-               toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Name", "Navigation.Menu.Tooltip", sonePlugin);
+               toadletContainer.getPageMaker().addNavigationCategory("/Sone/index.html", "Navigation.Menu.Sone.Name", "Navigation.Menu.Sone.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);
+                               toadletContainer.register(toadlet, "Navigation.Menu.Sone.Name", toadlet.path(), true, "Navigation.Menu.Sone.Item." + menuName + ".Name", "Navigation.Menu.Sone.Item." + menuName + ".Tooltip", false, toadlet);
                        } else {
                                toadletContainer.register(toadlet, null, toadlet.path(), true, false);
                        }
@@ -698,7 +705,7 @@ public class WebInterface implements CoreListener {
                for (PageToadlet pageToadlet : pageToadlets) {
                        toadletContainer.unregister(pageToadlet);
                }
-               toadletContainer.getPageMaker().removeNavigationCategory("Navigation.Menu.Name");
+               toadletContainer.getPageMaker().removeNavigationCategory("Navigation.Menu.Sone.Name");
        }
 
        /**
@@ -736,7 +743,7 @@ public class WebInterface implements CoreListener {
                                }
                        }
                } catch (IOException ioe1) {
-                       logger.log(Level.WARNING, "Could not parse post text: " + text, ioe1);
+                       logger.log(Level.WARNING, String.format("Could not parse post text: %s", text), ioe1);
                }
                return Filters.filteredSet(mentionedSones, Sone.LOCAL_SONE_FILTER);
        }
@@ -803,7 +810,7 @@ public class WebInterface implements CoreListener {
         * {@inheritDoc}
         */
        @Override
-       public void newReplyFound(Reply reply) {
+       public void newReplyFound(PostReply reply) {
                boolean isLocal = getCore().isLocalSone(reply.getSone());
                if (isLocal) {
                        localReplyNotification.add(reply);
@@ -812,7 +819,7 @@ public class WebInterface implements CoreListener {
                }
                if (!hasFirstStartNotification()) {
                        notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
-                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null)) {
+                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) {
                                mentionNotification.add(reply.getPost());
                                notificationManager.addNotification(mentionNotification);
                        }
@@ -843,7 +850,7 @@ public class WebInterface implements CoreListener {
         * {@inheritDoc}
         */
        @Override
-       public void markReplyKnown(Reply reply) {
+       public void markReplyKnown(PostReply reply) {
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
                mentionNotification.remove(reply.getPost());
@@ -864,15 +871,25 @@ public class WebInterface implements CoreListener {
        public void postRemoved(Post post) {
                newPostNotification.remove(post);
                localPostNotification.remove(post);
+               mentionNotification.remove(post);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       public void replyRemoved(Reply reply) {
+       public void replyRemoved(PostReply reply) {
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
+               if (!getMentionedSones(reply.getText()).isEmpty()) {
+                       boolean isMentioned = false;
+                       for (PostReply existingReply : getCore().getReplies(reply.getPost())) {
+                               isMentioned |= !reply.isKnown() && !getMentionedSones(existingReply.getText()).isEmpty();
+                       }
+                       if (!isMentioned) {
+                               mentionNotification.remove(reply.getPost());
+                       }
+               }
        }
 
        /**
@@ -988,66 +1005,4 @@ public class WebInterface implements CoreListener {
                notificationManager.addNotification(imageInsertFailedNotification);
        }
 
-       /**
-        * Template provider implementation that uses
-        * {@link WebInterface#createReader(String)} to load templates for
-        * inclusion.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private class ClassPathTemplateProvider implements Provider {
-
-               /** Cache for templates. */
-               private final Cache<String, Template> templateCache = new MemoryCache<String, Template>(new ValueRetriever<String, Template>() {
-
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       public CacheItem<Template> retrieve(String key) throws CacheException {
-                               Template template = findTemplate(key);
-                               if (template != null) {
-                                       return new DefaultCacheItem<Template>(template);
-                               }
-                               return null;
-                       }
-               });
-
-               /**
-                * {@inheritDoc}
-                */
-               @Override
-               @SuppressWarnings("synthetic-access")
-               public Template getTemplate(TemplateContext templateContext, String templateName) {
-                       try {
-                               return templateCache.get(templateName);
-                       } catch (CacheException ce1) {
-                               logger.log(Level.WARNING, "Could not get template for " + templateName + "!", ce1);
-                               return null;
-                       }
-               }
-
-               /**
-                * Locates a template in the class path.
-                *
-                * @param templateName
-                *            The name of the template to load
-                * @return The loaded template, or {@code null} if no template could be
-                *         found
-                */
-               @SuppressWarnings("synthetic-access")
-               private Template findTemplate(String templateName) {
-                       Reader templateReader = createReader("/templates/" + templateName);
-                       if (templateReader == null) {
-                               return null;
-                       }
-                       Template template = null;
-                       try {
-                               template = TemplateParser.parse(templateReader);
-                       } catch (TemplateException te1) {
-                               logger.log(Level.WARNING, "Could not parse template “" + templateName + "” for inclusion!", te1);
-                       }
-                       return template;
-               }
-
-       }
-
 }