From: David ‘Bombe’ Roden Date: Mon, 27 Jul 2015 18:28:26 +0000 (+0200) Subject: Merge branch 'feature/deliver-everything-from-filesystem' into next X-Git-Tag: 0.9.4^2~8 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=e80ee9c75042771d91bdd7095ca225adc6c11e6c;hp=48e66da8526834b24b80551819321e9a14fd1c8f Merge branch 'feature/deliver-everything-from-filesystem' into next --- diff --git a/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java b/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java new file mode 100644 index 0000000..11755e0 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java @@ -0,0 +1,41 @@ +package net.pterodactylus.sone.main; + +import java.io.File; + +import net.pterodactylus.sone.template.FilesystemTemplate; +import net.pterodactylus.sone.web.ReloadingPage; +import net.pterodactylus.util.template.FilesystemTemplateProvider; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateProvider; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Request; + +/** + * {@link Loaders} implementation that loads all resources from the filesystem. + * + * @author David ‘Bombe’ Roden + */ +public class DebugLoaders implements Loaders { + + private final String filesystemPath; + + public DebugLoaders(String filesystemPath) { + this.filesystemPath = filesystemPath; + } + + @Override + public Template loadTemplate(String path) { + return new FilesystemTemplate(new File(filesystemPath, path).getAbsolutePath()); + } + + @Override + public Page loadStaticPage(String basePath, String prefix, String mimeType) { + return new ReloadingPage(basePath, new File(filesystemPath, prefix).getAbsolutePath(), mimeType); + } + + @Override + public TemplateProvider getTemplateProvider() { + return new FilesystemTemplateProvider(new File(filesystemPath, "/templates/").getAbsolutePath()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/main/DefaultLoaders.java b/src/main/java/net/pterodactylus/sone/main/DefaultLoaders.java new file mode 100644 index 0000000..0f73216 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/main/DefaultLoaders.java @@ -0,0 +1,52 @@ +package net.pterodactylus.sone.main; + +import static net.pterodactylus.util.template.TemplateParser.parse; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; + +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.util.io.Closer; +import net.pterodactylus.util.template.ClassPathTemplateProvider; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateProvider; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Request; +import net.pterodactylus.util.web.StaticPage; + +/** + * Default {@link Loaders} implementation that loads resources from the classpath. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultLoaders implements Loaders { + + @Override + public Template loadTemplate(String path) { + InputStream templateInputStream = null; + Reader reader = null; + try { + templateInputStream = getClass().getResourceAsStream(path); + reader = new InputStreamReader(templateInputStream, "UTF-8"); + return parse(reader); + } catch (UnsupportedEncodingException uee1) { + throw new RuntimeException("UTF-8 not supported."); + } finally { + Closer.close(reader); + Closer.close(templateInputStream); + } + } + + @Override + public Page loadStaticPage(String pathPrefix, String basePath, String mimeType) { + return new StaticPage(pathPrefix, basePath, mimeType); + } + + @Override + public TemplateProvider getTemplateProvider() { + return new ClassPathTemplateProvider(WebInterface.class, "/templates/"); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/main/Loaders.java b/src/main/java/net/pterodactylus/sone/main/Loaders.java new file mode 100644 index 0000000..34ee1b1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/main/Loaders.java @@ -0,0 +1,22 @@ +package net.pterodactylus.sone.main; + +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateProvider; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Request; + +import com.google.inject.ImplementedBy; + +/** + * Defines loaders for resources that can be loaded from various locations. + * + * @author David ‘Bombe’ Roden + */ +@ImplementedBy(DefaultLoaders.class) +public interface Loaders { + + Template loadTemplate(String path); + Page loadStaticPage(String basePath, String prefix, String mimeType); + TemplateProvider getTemplateProvider(); + +} diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index ffaad9f..cbdca34 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -250,6 +250,12 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr bind(Context.class).toInstance(context); bind(getOptionalContextTypeLiteral()).toInstance(of(context)); bind(SonePlugin.class).toInstance(SonePlugin.this); + if (startConfiguration.getBooleanValue("Developer.LoadFromFilesystem").getValue(false)) { + String path = startConfiguration.getStringValue("Developer.FilesystemPath").getValue(null); + if (path != null) { + bind(Loaders.class).toInstance(new DebugLoaders(path)); + } + } bindListener(Matchers.any(), new TypeListener() { @Override diff --git a/src/main/java/net/pterodactylus/sone/template/FilesystemTemplate.java b/src/main/java/net/pterodactylus/sone/template/FilesystemTemplate.java new file mode 100644 index 0000000..473c191 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/template/FilesystemTemplate.java @@ -0,0 +1,145 @@ +package net.pterodactylus.sone.template; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import net.pterodactylus.util.template.Part; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.template.TemplateException; +import net.pterodactylus.util.template.TemplateParser; + +import freenet.support.io.Closer; + +import com.google.common.base.Charsets; + +/** + * {@link Template} implementation that can be reloaded from the filesystem. + * + * @author David ‘Bombe’ Roden + */ +public class FilesystemTemplate extends Template { + + private final String filename; + private final AtomicReference lastTemplate = new AtomicReference(); + private final TemplateContext initialContext = new TemplateContext(); + private final List parts = new ArrayList(); + + public FilesystemTemplate(String filename) { + this.filename = filename; + } + + @Override + public TemplateContext getInitialContext() { + loadTemplate(); + return initialContext; + } + + private void loadTemplate() { + File templateFile = new File(filename); + if (!templateFile.exists()) { + throw new TemplateFileNotFoundException(filename); + } + if (templateWasLoaded() && !templateFileHasBeenModifiedAfterLoading(templateFile)) { + return; + } + InputStream templateInputStream = null; + Reader templateReader = null; + try { + templateInputStream = new FileInputStream(templateFile); + templateReader = new InputStreamReader(templateInputStream, Charsets.UTF_8); + Template template = TemplateParser.parse(templateReader); + lastTemplate.set(new LastLoadedTemplate(template)); + template.getInitialContext().mergeContext(initialContext); + for (Part part : parts) { + template.add(part); + } + } catch (FileNotFoundException e) { + throw new TemplateFileNotFoundException(filename); + } finally { + Closer.close(templateReader); + Closer.close(templateInputStream); + } + } + + private boolean templateWasLoaded() { + return lastTemplate.get() != null; + } + + private boolean templateFileHasBeenModifiedAfterLoading(File templateFile) { + return templateFile.lastModified() > lastTemplate.get().getLoadTime(); + } + + @Override + public void add(Part part) { + loadTemplate(); + parts.add(part); + lastTemplate.get().getTemplate().add(part); + } + + @Override + public void render(TemplateContext templateContext, Writer writer) throws TemplateException { + loadTemplate(); + lastTemplate.get().getTemplate().render(templateContext, writer); + } + + @Override + public Iterator iterator() { + loadTemplate(); + return lastTemplate.get().getTemplate().iterator(); + } + + @Override + public int getLine() { + loadTemplate(); + return lastTemplate.get().getTemplate().getLine(); + } + + @Override + public int getColumn() { + loadTemplate(); + return lastTemplate.get().getTemplate().getColumn(); + } + + private static class LastLoadedTemplate { + + private final Template template; + private final long loadTime = System.currentTimeMillis(); + + private LastLoadedTemplate(Template template) { + this.template = template; + } + + public Template getTemplate() { + return template; + } + + public long getLoadTime() { + return loadTime; + } + + } + + /** + * Exception that signals that a template file could not be found. + * + * @author David ‘Bombe’ Roden + */ + public static class TemplateFileNotFoundException extends RuntimeException { + + public TemplateFileNotFoundException(String filename) { + super(filename); + } + + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java b/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java new file mode 100644 index 0000000..0c8f516 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java @@ -0,0 +1,88 @@ +/* + * Sone - ReloadingPage.java - Copyright © 2010–2015 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.pterodactylus.util.io.Closer; +import net.pterodactylus.util.io.StreamCopier; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Request; +import net.pterodactylus.util.web.Response; + +/** + * {@link Page} implementation that delivers static files from the filesystem. + * + * @param + * The type of the request + * @author David ‘Bombe’ Roden + */ +public class ReloadingPage implements Page { + + private final String pathPrefix; + private final String filesystemPath; + private final String mimeType; + + public ReloadingPage(String pathPrefix, String filesystemPathPrefix, String mimeType) { + this.pathPrefix = pathPrefix; + this.filesystemPath = filesystemPathPrefix; + this.mimeType = mimeType; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return pathPrefix; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrefixPage() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(REQ request, Response response) throws IOException { + String path = request.getUri().getPath(); + int lastSlash = path.lastIndexOf('/'); + String filename = path.substring(lastSlash + 1); + InputStream fileInputStream = new FileInputStream(new File(filesystemPath, filename)); + if (fileInputStream == null) { + return response.setStatusCode(404).setStatusText("Not found."); + } + OutputStream contentOutputStream = response.getContent(); + try { + StreamCopier.copy(fileInputStream, contentOutputStream); + } finally { + Closer.close(fileInputStream); + Closer.close(contentOutputStream); + } + return response.setStatusCode(200).setStatusText("OK").setContentType(mimeType); + } +} diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index fbba8e0..beba513 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -21,11 +21,7 @@ import static java.util.logging.Logger.getLogger; import static net.pterodactylus.util.template.TemplateParser.parse; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; import java.io.StringReader; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -72,6 +68,7 @@ import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.L10nFilter; import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.Trust; +import net.pterodactylus.sone.main.Loaders; import net.pterodactylus.sone.main.ReparseFilter; import net.pterodactylus.sone.main.SonePlugin; import net.pterodactylus.sone.notify.ListNotification; @@ -129,11 +126,9 @@ 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.io.Closer; 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; @@ -150,7 +145,6 @@ import net.pterodactylus.util.template.TemplateContextFactory; import net.pterodactylus.util.template.TemplateProvider; import net.pterodactylus.util.template.XmlFilter; import net.pterodactylus.util.web.RedirectPage; -import net.pterodactylus.util.web.StaticPage; import net.pterodactylus.util.web.TemplatePage; import com.google.common.collect.Collections2; @@ -176,6 +170,9 @@ public class WebInterface { /** The logger. */ 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 = new NotificationManager(); @@ -246,8 +243,9 @@ public class WebInterface { * The Sone plugin */ @Inject - public WebInterface(SonePlugin sonePlugin) { + public WebInterface(SonePlugin sonePlugin, Loaders loaders) { this.sonePlugin = sonePlugin; + this.loaders = loaders; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); soneTextParser = new SoneTextParser(getCore(), getCore()); @@ -286,60 +284,45 @@ public class WebInterface { templateContextFactory.addFilter("mod", new ModFilter()); templateContextFactory.addFilter("paginate", new PaginationFilter()); templateContextFactory.addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER); - templateContextFactory.addProvider(new ClassPathTemplateProvider(WebInterface.class, "/templates/")); + templateContextFactory.addProvider(loaders.getTemplateProvider()); templateContextFactory.addTemplateObject("webInterface", this); templateContextFactory.addTemplateObject("formPassword", formPassword); /* create notifications. */ - Template newSoneNotificationTemplate = parseTemplate("/templates/notify/newSoneNotification.html"); + Template newSoneNotificationTemplate = loaders.loadTemplate("/templates/notify/newSoneNotification.html"); newSoneNotification = new ListNotification("new-sone-notification", "sones", newSoneNotificationTemplate, false); - Template newPostNotificationTemplate = parseTemplate("/templates/notify/newPostNotification.html"); + Template newPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html"); newPostNotification = new ListNotification("new-post-notification", "posts", newPostNotificationTemplate, false); - Template localPostNotificationTemplate = parseTemplate("/templates/notify/newPostNotification.html"); + Template localPostNotificationTemplate = loaders.loadTemplate("/templates/notify/newPostNotification.html"); localPostNotification = new ListNotification("local-post-notification", "posts", localPostNotificationTemplate, false); - Template newReplyNotificationTemplate = parseTemplate("/templates/notify/newReplyNotification.html"); + Template newReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); newReplyNotification = new ListNotification("new-reply-notification", "replies", newReplyNotificationTemplate, false); - Template localReplyNotificationTemplate = parseTemplate("/templates/notify/newReplyNotification.html"); + Template localReplyNotificationTemplate = loaders.loadTemplate("/templates/notify/newReplyNotification.html"); localReplyNotification = new ListNotification("local-reply-notification", "replies", localReplyNotificationTemplate, false); - Template mentionNotificationTemplate = parseTemplate("/templates/notify/mentionNotification.html"); + Template mentionNotificationTemplate = loaders.loadTemplate("/templates/notify/mentionNotification.html"); mentionNotification = new ListNotification("mention-notification", "posts", mentionNotificationTemplate, false); - Template lockedSonesTemplate = parseTemplate("/templates/notify/lockedSonesNotification.html"); + Template lockedSonesTemplate = loaders.loadTemplate("/templates/notify/lockedSonesNotification.html"); lockedSonesNotification = new ListNotification("sones-locked-notification", "sones", lockedSonesTemplate); - Template newVersionTemplate = parseTemplate("/templates/notify/newVersionNotification.html"); + Template newVersionTemplate = loaders.loadTemplate("/templates/notify/newVersionNotification.html"); newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate); - Template insertingImagesTemplate = parseTemplate("/templates/notify/inserting-images-notification.html"); + Template insertingImagesTemplate = loaders.loadTemplate("/templates/notify/inserting-images-notification.html"); insertingImagesNotification = new ListNotification("inserting-images-notification", "images", insertingImagesTemplate); - Template insertedImagesTemplate = parseTemplate("/templates/notify/inserted-images-notification.html"); + Template insertedImagesTemplate = loaders.loadTemplate("/templates/notify/inserted-images-notification.html"); insertedImagesNotification = new ListNotification("inserted-images-notification", "images", insertedImagesTemplate); - Template imageInsertFailedTemplate = parseTemplate("/templates/notify/image-insert-failed-notification.html"); + Template imageInsertFailedTemplate = loaders.loadTemplate("/templates/notify/image-insert-failed-notification.html"); imageInsertFailedNotification = new ListNotification("image-insert-failed-notification", "images", imageInsertFailedTemplate); } - private Template parseTemplate(String resourceName) { - InputStream templateInputStream = null; - Reader reader = null; - try { - templateInputStream = getClass().getResourceAsStream(resourceName); - reader = new InputStreamReader(templateInputStream, "UTF-8"); - return parse(reader); - } catch (UnsupportedEncodingException uee1) { - throw new RuntimeException("UTF-8 not supported."); - } finally { - Closer.close(reader); - Closer.close(templateInputStream); - } - } - // // ACCESSORS // @@ -528,7 +511,7 @@ public class WebInterface { */ public void setFirstStart(boolean firstStart) { if (firstStart) { - Template firstStartNotificationTemplate = parseTemplate("/templates/notify/firstStartNotification.html"); + Template firstStartNotificationTemplate = loaders.loadTemplate("/templates/notify/firstStartNotification.html"); Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate); notificationManager.addNotification(firstStartNotification); } @@ -543,7 +526,7 @@ public class WebInterface { */ public void setNewConfig(boolean newConfig) { if (newConfig && !hasFirstStartNotification()) { - Template configNotReadNotificationTemplate = parseTemplate("/templates/notify/configNotReadNotification.html"); + Template configNotReadNotificationTemplate = loaders.loadTemplate("/templates/notify/configNotReadNotification.html"); Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate); notificationManager.addNotification(configNotReadNotification); } @@ -574,7 +557,7 @@ public class WebInterface { registerToadlets(); /* notification templates. */ - Template startupNotificationTemplate = parseTemplate("/templates/notify/startupNotification.html"); + Template startupNotificationTemplate = loaders.loadTemplate("/templates/notify/startupNotification.html"); final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate); notificationManager.addNotification(startupNotification); @@ -587,7 +570,7 @@ public class WebInterface { } }, 2, TimeUnit.MINUTES); - Template wotMissingNotificationTemplate = parseTemplate("/templates/notify/wotMissingNotification.html"); + Template wotMissingNotificationTemplate = loaders.loadTemplate("/templates/notify/wotMissingNotification.html"); final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate); ticker.scheduleAtFixedRate(new Runnable() { @@ -621,37 +604,37 @@ public class WebInterface { */ private void registerToadlets() { Template emptyTemplate = parse(new StringReader("")); - Template loginTemplate = parseTemplate("/templates/login.html"); - Template indexTemplate = parseTemplate("/templates/index.html"); - Template newTemplate = parseTemplate("/templates/new.html"); - Template knownSonesTemplate = parseTemplate("/templates/knownSones.html"); - Template createSoneTemplate = parseTemplate("/templates/createSone.html"); - Template createPostTemplate = parseTemplate("/templates/createPost.html"); - Template createReplyTemplate = parseTemplate("/templates/createReply.html"); - Template bookmarksTemplate = parseTemplate("/templates/bookmarks.html"); - Template searchTemplate = parseTemplate("/templates/search.html"); - Template editProfileTemplate = parseTemplate("/templates/editProfile.html"); - Template editProfileFieldTemplate = parseTemplate("/templates/editProfileField.html"); - Template deleteProfileFieldTemplate = parseTemplate("/templates/deleteProfileField.html"); - Template viewSoneTemplate = parseTemplate("/templates/viewSone.html"); - Template viewPostTemplate = parseTemplate("/templates/viewPost.html"); - Template deletePostTemplate = parseTemplate("/templates/deletePost.html"); - Template deleteReplyTemplate = parseTemplate("/templates/deleteReply.html"); - Template deleteSoneTemplate = parseTemplate("/templates/deleteSone.html"); - Template imageBrowserTemplate = parseTemplate("/templates/imageBrowser.html"); - Template createAlbumTemplate = parseTemplate("/templates/createAlbum.html"); - Template deleteAlbumTemplate = parseTemplate("/templates/deleteAlbum.html"); - Template deleteImageTemplate = parseTemplate("/templates/deleteImage.html"); - Template noPermissionTemplate = parseTemplate("/templates/noPermission.html"); - Template emptyImageTitleTemplate = parseTemplate("/templates/emptyImageTitle.html"); - Template emptyAlbumTitleTemplate = parseTemplate("/templates/emptyAlbumTitle.html"); - Template optionsTemplate = parseTemplate("/templates/options.html"); - Template rescueTemplate = parseTemplate("/templates/rescue.html"); - Template aboutTemplate = parseTemplate("/templates/about.html"); - Template invalidTemplate = parseTemplate("/templates/invalid.html"); - Template postTemplate = parseTemplate("/templates/include/viewPost.html"); - Template replyTemplate = parseTemplate("/templates/include/viewReply.html"); - Template openSearchTemplate = parseTemplate("/templates/xml/OpenSearch.xml"); + Template loginTemplate = loaders.loadTemplate("/templates/login.html"); + Template indexTemplate = loaders.loadTemplate("/templates/index.html"); + Template newTemplate = loaders.loadTemplate("/templates/new.html"); + Template knownSonesTemplate = loaders.loadTemplate("/templates/knownSones.html"); + Template createSoneTemplate = loaders.loadTemplate("/templates/createSone.html"); + Template createPostTemplate = loaders.loadTemplate("/templates/createPost.html"); + Template createReplyTemplate = loaders.loadTemplate("/templates/createReply.html"); + Template bookmarksTemplate = loaders.loadTemplate("/templates/bookmarks.html"); + Template searchTemplate = loaders.loadTemplate("/templates/search.html"); + Template editProfileTemplate = loaders.loadTemplate("/templates/editProfile.html"); + Template editProfileFieldTemplate = loaders.loadTemplate("/templates/editProfileField.html"); + Template deleteProfileFieldTemplate = loaders.loadTemplate("/templates/deleteProfileField.html"); + Template viewSoneTemplate = loaders.loadTemplate("/templates/viewSone.html"); + Template viewPostTemplate = loaders.loadTemplate("/templates/viewPost.html"); + Template deletePostTemplate = loaders.loadTemplate("/templates/deletePost.html"); + Template deleteReplyTemplate = loaders.loadTemplate("/templates/deleteReply.html"); + Template deleteSoneTemplate = loaders.loadTemplate("/templates/deleteSone.html"); + Template imageBrowserTemplate = loaders.loadTemplate("/templates/imageBrowser.html"); + Template createAlbumTemplate = loaders.loadTemplate("/templates/createAlbum.html"); + Template deleteAlbumTemplate = loaders.loadTemplate("/templates/deleteAlbum.html"); + Template deleteImageTemplate = loaders.loadTemplate("/templates/deleteImage.html"); + Template noPermissionTemplate = loaders.loadTemplate("/templates/noPermission.html"); + Template emptyImageTitleTemplate = loaders.loadTemplate("/templates/emptyImageTitle.html"); + Template emptyAlbumTitleTemplate = loaders.loadTemplate("/templates/emptyAlbumTitle.html"); + Template optionsTemplate = loaders.loadTemplate("/templates/options.html"); + Template rescueTemplate = loaders.loadTemplate("/templates/rescue.html"); + Template aboutTemplate = loaders.loadTemplate("/templates/about.html"); + Template invalidTemplate = loaders.loadTemplate("/templates/invalid.html"); + Template postTemplate = loaders.loadTemplate("/templates/include/viewPost.html"); + Template replyTemplate = loaders.loadTemplate("/templates/include/viewReply.html"); + Template openSearchTemplate = loaders.loadTemplate("/templates/xml/OpenSearch.xml"); PageToadletFactory pageToadletFactory = new PageToadletFactory(sonePlugin.pluginRespirator().getHLSimpleClient(), "/Sone/"); pageToadlets.add(pageToadletFactory.createPageToadlet(new RedirectPage("", "index.html"))); @@ -700,9 +683,9 @@ public class WebInterface { pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("emptyAlbumTitle.html", emptyAlbumTitleTemplate, "Page.EmptyAlbumTitle.Title", this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationPage(emptyTemplate, this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new SoneTemplatePage("invalid.html", invalidTemplate, "Page.Invalid.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(loaders.loadStaticPage("css/", "/static/css/", "text/css"))); + pageToadlets.add(pageToadletFactory.createPageToadlet(loaders.loadStaticPage("javascript/", "/static/javascript/", "text/javascript"))); + pageToadlets.add(pageToadletFactory.createPageToadlet(loaders.loadStaticPage("images/", "/static/images/", "image/png"))); pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetImagePage(this))); pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this))); @@ -795,7 +778,7 @@ public class WebInterface { synchronized (soneInsertNotifications) { TemplateNotification templateNotification = soneInsertNotifications.get(sone); if (templateNotification == null) { - templateNotification = new TemplateNotification(parseTemplate("/templates/notify/soneInsertNotification.html")); + templateNotification = new TemplateNotification(loaders.loadTemplate("/templates/notify/soneInsertNotification.html")); templateNotification.set("insertSone", sone); soneInsertNotifications.put(sone, templateNotification); } diff --git a/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java b/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java new file mode 100644 index 0000000..c3fa7e9 --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java @@ -0,0 +1,89 @@ +package net.pterodactylus.sone.main; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Mockito.mock; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; + +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.template.TemplateProvider; +import net.pterodactylus.util.web.Method; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Response; + +import freenet.clients.http.ToadletContext; +import freenet.support.api.HTTPRequest; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Unit test for {@link DebugLoaders}. + * + * @author David ‘Bombe’ Roden + */ +public class DebugLoadersTest { + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final StringWriter stringWriter = new StringWriter(); + private final TemplateContext templateContext = new TemplateContext(); + private Loaders loaders; + + @Before + public void setupLoader() throws IOException { + String templatePath = temporaryFolder.newFolder("temps").getPath(); + loaders = new DebugLoaders(templatePath); + File templateFile = new File(templatePath, "template.txt"); + Files.write("<%if foo>foo<%else>bar<%/if>", templateFile, Charsets.UTF_8); + new File(templatePath, "templates").mkdir(); + File secondTemplateFile = new File(templatePath, "templates/template.txt"); + Files.write("<%if foo>foo<%else>bar<%/if>", secondTemplateFile, Charsets.UTF_8); + } + + @Test + public void debugLoaderCanLoadTemplatesFromFilesystem() throws IOException { + Template template = loaders.loadTemplate("/template.txt"); + template.render(templateContext, stringWriter); + assertThat(stringWriter.toString(), is("bar")); + } + + @Test + public void staticPageIsServedFromFilesystem() throws URISyntaxException, IOException { + Page page = loaders.loadStaticPage("text/", "", "text/plain"); + URI uri = new URI("http://some.host/text/template.txt"); + Method method = Method.GET; + HTTPRequest httpRequest = mock(HTTPRequest.class); + ToadletContext toadletContext = mock(ToadletContext.class); + FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext); + OutputStream outputStream = new ByteArrayOutputStream(); + Response response = new Response(outputStream); + page.handleRequest(request, response); + assertThat(response.getContentType(), startsWith("text/plain")); + assertThat(response.getStatusCode(), is(200)); + } + + @Test + public void templateProviderLocatesTemplatesInFileSystem() { + TemplateProvider templateProvider = loaders.getTemplateProvider(); + Template template = templateProvider.getTemplate(templateContext, "template.txt"); + template.render(templateContext, stringWriter); + assertThat(stringWriter.toString(), is("bar")); + } + +} diff --git a/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java b/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java new file mode 100644 index 0000000..0f0a9f1 --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java @@ -0,0 +1,69 @@ +package net.pterodactylus.sone.main; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Mockito.mock; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; + +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.template.TemplateProvider; +import net.pterodactylus.util.web.Method; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Response; + +import freenet.clients.http.ToadletContext; +import freenet.support.api.HTTPRequest; + +import org.junit.Test; + +/** + * Unit test for {@link DefaultLoaders}. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultLoadersTest { + + private final Loaders loaders = new DefaultLoaders(); + private final StringWriter stringWriter = new StringWriter(); + private final TemplateContext templateContext = new TemplateContext(); + + @Test + public void templateCanBeLoadedFromTheClasspath() { + Template template = loaders.loadTemplate("/net/pterodactylus/sone/main/template.txt"); + template.render(templateContext, stringWriter); + assertThat(stringWriter.toString(), is("Template. bar\n")); + } + + @Test + public void staticPageIsServedFromClasspath() throws IOException, URISyntaxException { + Page staticPage = loaders.loadStaticPage("text/", "/net/pterodactylus/sone/main/", "text/plain"); + URI uri = new URI("http://some.host/text/template.txt"); + Method method = Method.GET; + HTTPRequest httpRequest = mock(HTTPRequest.class); + ToadletContext toadletContext = mock(ToadletContext.class); + FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext); + OutputStream outputStream = new ByteArrayOutputStream(); + Response response = new Response(outputStream); + staticPage.handleRequest(request, response); + assertThat(response.getContentType(), startsWith("text/plain")); + assertThat(response.getStatusCode(), is(200)); + } + + @Test + public void templateIsLocatedInClasspath() { + TemplateProvider templateProvider = loaders.getTemplateProvider(); + Template template = templateProvider.getTemplate(templateContext, "about.html"); + assertThat(template, notNullValue()); + } + +} diff --git a/src/test/java/net/pterodactylus/sone/template/FilesystemTemplateTest.java b/src/test/java/net/pterodactylus/sone/template/FilesystemTemplateTest.java new file mode 100644 index 0000000..cd989eb --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/template/FilesystemTemplateTest.java @@ -0,0 +1,111 @@ +package net.pterodactylus.sone.template; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import net.pterodactylus.util.template.Part; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.template.TemplateException; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link FilesystemTemplate}. + * + * @author David ‘Bombe’ Roden + */ +public class FilesystemTemplateTest { + + private final File tempFile; + private final FilesystemTemplate filesystemTemplate; + private final AtomicReference stringWriter = new AtomicReference(new StringWriter()); + private final TemplateContext templateContext = new TemplateContext(); + + public FilesystemTemplateTest() throws IOException { + tempFile = File.createTempFile("template-", ".dat"); + writeTemplate("Text"); + filesystemTemplate = new FilesystemTemplate(tempFile.getAbsolutePath()); + } + + private void writeTemplate(String text) throws IOException { + Files.write(text + ".<%foreach values value><% value><%/foreach>", tempFile, Charsets.UTF_8); + } + + @Before + public void setupTemplateContext() { + templateContext.set("values", Arrays.asList("a", 1)); + } + + @Test(expected = FilesystemTemplate.TemplateFileNotFoundException.class) + public void loadingTemplateFromNonExistingFileThrowsException() throws IOException { + FilesystemTemplate filesystemTemplate = new FilesystemTemplate("/a/b/c.dat"); + filesystemTemplate.getInitialContext(); + } + + @Test + public void templateCanBeLoadedFromTheFilesystem() { + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("Text.a1")); + } + + @Test + public void templateCanBeReloaded() throws IOException, InterruptedException { + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("Text.a1")); + Thread.sleep(1000); + writeTemplate("New"); + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("New.a1")); + } + + @Test + public void templateIsNotReloadedIfNotChanged() { + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("Text.a1")); + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("Text.a1")); + } + + private String getRenderedString() { + String renderedString = stringWriter.get().toString(); + stringWriter.set(new StringWriter()); + return renderedString; + } + + @Test + public void initialContextIsCopiedToReloadedTemplates() throws IOException, InterruptedException { + filesystemTemplate.getInitialContext().set("values", "test"); + Thread.sleep(1000); + writeTemplate("New"); + assertThat(filesystemTemplate.getInitialContext().get("values"), is((Object) "test")); + } + + @Test + public void partsAreCopiedToReloadedTemplates() throws InterruptedException, IOException { + filesystemTemplate.add(new Part() { + @Override + public void render(TemplateContext templateContext, Writer writer) throws TemplateException { + try { + writer.write(".Test"); + } catch (IOException e) { + throw new TemplateException(e); + } + } + }); + Thread.sleep(1000); + writeTemplate("New"); + filesystemTemplate.render(templateContext, stringWriter.get()); + assertThat(getRenderedString(), is("New.a1.Test")); + } + +} diff --git a/src/test/resources/net/pterodactylus/sone/main/template.txt b/src/test/resources/net/pterodactylus/sone/main/template.txt new file mode 100644 index 0000000..c2377d2 --- /dev/null +++ b/src/test/resources/net/pterodactylus/sone/main/template.txt @@ -0,0 +1 @@ +Template. <%if foo>foo<%else>bar<%/if> diff --git a/template.txt b/template.txt new file mode 100644 index 0000000..a26591c --- /dev/null +++ b/template.txt @@ -0,0 +1 @@ +<%if foo>foo<%else>bar<%/if> \ No newline at end of file